Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> */
final class CheckGlobalThis implements Callback {
static final DiagnosticType GLOBAL_THIS = DiagnosticType.warning(
"JSC_USED_GLOBAL_THIS",
"dangerous use of the global 'this' object");
private final AbstractCompiler compiler;
private final CheckLevel level;
/**
* If {@code assignLhsChild != null}, then the node being traversed is
* a descendant of the first child of an ASSIGN node. assignLhsChild's
* parent is this ASSIGN node.
*/
private Node assignLhsChild = null;
CheckGlobalThis(AbstractCompiler compiler, CheckLevel level) {
this.compiler = compiler;
this.level = level;
// Only traverse the right side if it's not an assignment to a prototype
// property or subproperty.
if (lhs.getType() == Token.GETPROP) {
if (lhs.getLastChild().getString().equals("prototype")) {
return false;
}
String leftName = lhs.getQualifiedName();
if (leftName != null && leftName.contains(".prototype.")) {
return false;
}
}
JSDocInfo jsDoc = n.getJSDocInfo();
Node parent = n.getParent();
if (jsDoc == null) {
int parentType = parent.getType();
if (parentType == Token.NAME || parentType == Token.ASSIGN) {
jsDoc = parent.getJSDocInfo();
if (jsDoc == null && parentType == Token.NAME) {
Node gramps = parent.getParent();
if (gramps.getType() == Token.VAR) {
jsDoc = gramps.getJSDocInfo();
}
}
}
}
return jsDoc;
}
}
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>
endLine();
}
@Override
void endCaseBody() {
super.endCaseBody();
indent--;
endStatement();
}
@Override
void appendOp(String op, boolean binOp) {
if (binOp) {
if (getLastChar() != ' ') {
append(" ");
}
append(op);
append(" ");
} else {
append(op);
}
}
/**
* If the body of a for loop or the then clause of an if statement has
* a single statement, should it be wrapped in a block?
* {@inheritDoc}
*/
@Override
boolean shouldPreserveExtraBlocks() {
// When pretty-printing, always place the statement in its own block
// so it is printed on a separate line. This allows breakpoints to be
// placed on the statement.
return true;
}
/**
* @return The TRY node for the specified CATCH node.
*/
private Node getTryForCatch(Node n) {
return n.getParent().getParent();
}
/**
* @return Whether the a line break should be added after the specified
* BLOCK.
*/
@Override
boolean breakAfterBlockFor(Node n, boolean isStatementContext) {
Preconditions.checkState(n.getType() == Token.BLOCK);
Node parent = n.getParent();
if (parent != null) {
int type = parent.getType();
switch (type) {
case Token.DO:
// Don't break before 'while' in DO-WHILE statements.
return false;
case Token.FUNCTION:
// FUNCTIONs are handled separately, don't break here.
return false;
case Token.TRY:
// Don't break before catch
return n != parent.getFirstChild();
case Token.CATCH:
// Don't break before finally
return !NodeUtil.hasFinally(getTryForCatch(parent));
case Token.IF:
// Don't break before else
return n == parent.getLastChild();
}
}
return true;
}
}
static class CompactCodePrinter
extends MappedCodePrinter
implements HasGetCode {
// The CompactCodePrinter tries to emit just enough newlines to stop there
// being lines longer than the threshold. Since the output is going to be
// gz
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>(condition, blindScope, NE);
} else {
return caseEquality(condition, blindScope, EQ);
}
case Token.SHEQ:
if (outcome) {
return caseEquality(condition, blindScope, SHEQ);
} else {
return caseEquality(condition, blindScope, SHNE);
}
case Token.SHNE:
if (outcome) {
return caseEquality(condition, blindScope, SHNE);
} else {
return caseEquality(condition, blindScope, SHEQ);
}
case Token.NAME:
case Token.GETPROP:
return caseNameOrGetProp(condition, blindScope, outcome);
case Token.ASSIGN:
return firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild(),
firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild().getNext(), blindScope, outcome),
outcome);
case Token.NOT:
return firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild(), blindScope, !outcome);
case Token.LE:
case Token.LT:
case Token.GE:
case Token.GT:
if (outcome) {
return caseEquality(condition, blindScope, INEQ);
}
break;
case Token.INSTANCEOF:
return caseInstanceOf(
condition.getFirstChild(), condition.getLastChild(), blindScope,
outcome);
case Token.IN:
if (outcome && condition.getFirstChild().getType() == Token.STRING) {
return caseIn(condition.getLastChild(),
condition.getFirstChild().getString(), blindScope);
}
break;
case Token.CASE:
Node left =
condition.getParent().getFirstChild(); // the switch condition
Node right = condition.getFirstChild();
if (outcome) {
return caseEquality(left, right, blindScope, SHEQ);
} else {
return caseEquality(left, right, blindScope, SHNE);
}
}
return nextPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome);
}
private FlowScope caseEquality(Node condition, FlowScope blindScope,
Function<TypePair, TypePair> merging) {
return caseEquality(condition.getFirstChild(), condition.getLastChild(),
blindScope, merging);
}
private FlowScope caseEquality
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>compiler));
}
}
/**
* Covert EXPR_VOID to EXPR_RESULT to simplify the rest of the code.
*/
private void normalizeNodeTypes(Node n) {
if (n.getType() == Token.EXPR_VOID) {
n.setType(Token.EXPR_RESULT);
reportChange();
}
// Remove unused properties to minimize differences between ASTs
// produced by the two parsers.
if (n.getType() == Token.FUNCTION) {
Preconditions.checkState(n.getProp(Node.FUNCTION_PROP) == null);
}
normalizeBlocks(n);
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
// This pass is run during the CompilerTestCase validation, so this
// parent pointer check serves as a more general check.
Preconditions.checkState(child.getParent() == n);
normalizeNodeTypes(child);
}
}
/**
* Add blocks to IF, WHILE, DO, etc.
*/
private void normalizeBlocks(Node n) {
if (NodeUtil.isControlStructure(n)
&& n.getType() != Token.LABEL
&& n.getType() != Token.SWITCH) {
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (NodeUtil.isControlStructureCodeBlock(n,c) &&
c.getType() != Token.BLOCK) {
Node newBlock = new Node(Token.BLOCK, n.getLineno(), n.getCharno());
newBlock.copyInformationFrom(n);
n.replaceChild(c, newBlock);
if (c.getType() != Token.EMPTY) {
newBlock.addChildrenToFront(c);
} else {
newBlock.setWasEmptyNode(true);
}
c = newBlock;
reportChange();
}
}
}
}
/**
* Normalize where annotations appear on the AST. Copies
* around existing JSDoc annotations as well as internal annotations.
*/
static class PrepareAnnotations
extends NodeTraversal.AbstractPostOrderCallback {
private final CodingConvention convention;
PrepareAnnotations(AbstractCompiler compiler) {
this.convention = compiler.getCodingConvention();
}
/**
*
* In the AST that R
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>hino gives us, it needs to make a distinction
* between jsdoc on the object literal node and jsdoc on the object literal
* value. For example,
* <pre>
* var x = {
* / JSDOC /
* a: 'b',
* c: / JSDOC / 'd'
* };
* </pre>
*
* But in few narrow cases (in particular, function literals), it's
* a lot easier for us if the doc is attached to the value.
*/
public void visit(NodeTraversal t, Node n, Node parent) {
int nType = n.getType();
switch (nType) {
case Token.NAME:
case Token.STRING:
String nString = n.getString();
if (nType == Token.NAME &&
n.getParent().getType() == Token.CALL &&
"eval".equals(nString)) {
n.putBooleanProp(Node.DIRECT_EVAL, true);
}
if (convention.isConstant(nString)) {
n.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
break;
case Token.FUNCTION:
JSDocInfo fnInfo = n.getJSDocInfo();
if (fnInfo == null) {
// Look for the info on other nodes.
if (parent.getType() == Token.ASSIGN) {
// on ASSIGNs
fnInfo = parent.getJSDocInfo();
} else if (parent.getType() == Token.NAME) {
// on var NAME = function() { ... };
fnInfo = parent.getParent().getJSDocInfo();
}
}
// Compute which function parameters are optional and
// which are var_args.
Node args = n.getFirstChild().getNext();
for (Node arg = args.getFirstChild();
arg != null;
arg = arg.getNext()) {
String argName = arg.getString();
JSTypeExpression typeExpr = fnInfo == null ?
null : fnInfo.getParameterType(argName);
if (convention.isOptionalParameter(arg) ||
typeExpr != null && typeExpr.isOptionalArg()) {
arg.putBooleanProp(Node.IS_OPTIONAL_PARAM, true);
}
if (convention.isVarArgsParameter
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> variable is still declared,
// but no longer assigned to a value, e.g.,
// DEF_FOO = 5; // becomes "5;"
// We can't remove the ASSIGN/VAR when we're still visiting its
// children, so we'll have to come back later to remove it.
refInfo.name.removeRef(ref);
lvalueToRemoveLater = valParent;
}
break;
default:
if (t.inGlobalScope()) {
// Treat this as a reference to a define in the global scope.
// After this point, the define must not be reassigned,
// or it's an error.
DefineInfo info = assignableDefines.get(fullName);
if (info != null) {
setDefineInfoNotAssignable(info, t);
assignableDefines.remove(fullName);
}
}
break;
}
}
if (!t.inGlobalScope() &&
n.getJSDocInfo() != null && n.getJSDocInfo().isDefine()) {
// warn about @define annotations in local scopes
compiler.report(
t.makeError(n, NON_GLOBAL_DEFINE_INIT_ERROR, ""));
}
if (lvalueToRemoveLater == n) {
lvalueToRemoveLater = null;
if (n.getType() == Token.ASSIGN) {
Node last = n.getLastChild();
n.removeChild(last);
parent.replaceChild(n, last);
} else {
Preconditions.checkState(n.getType() == Token.NAME);
n.removeChild(n.getFirstChild());
}
compiler.reportCodeChange();
}
if (n.getType() == Token.CALL) {
if (t.inGlobalScope()) {
// If there's a function call in the global scope,
// we just say it's unsafe and freeze all the defines.
//
// NOTE(nicksantos): We could be a lot smarter here. For example,
// ReplaceOverriddenVars keeps a call graph of all functions and
// which functions/variables that they reference, and tries
// to statically determine which functions are "safe" and which
// are not. But this would be overkill, expecially because
// the intended use of defines is with config_files, where
// all the
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> defines are at the top of the bundle.
for (DefineInfo info : assignableDefines.values()) {
setDefineInfoNotAssignable(info, t);
}
assignableDefines.clear();
}
}
updateAssignAllowedStack(n, false);
}
/**
* Determines whether assignment to a define should be allowed
* in the subtree of the given node, and if not, records that fact.
*
* @param n The node whose subtree we're about to enter or exit.
* @param entering True if we're entering the subtree, false otherwise.
*/
private void updateAssignAllowedStack(Node n, boolean entering) {
switch (n.getType()) {
case Token.CASE:
case Token.FOR:
case Token.FUNCTION:
case Token.HOOK:
case Token.IF:
case Token.SWITCH:
case Token.WHILE:
if (entering) {
assignAllowed.push(0);
} else {
assignAllowed.remove();
}
break;
}
}
/**
* Determines whether assignment to a define should be allowed
* at the current point of the traversal.
*/
private boolean isAssignAllowed() {
return assignAllowed.element() == 1;
}
/**
* Tracks the given define.
*
* @param t The current traversal, for context.
* @param name The full name for this define.
* @param value The value assigned to the define.
* @param valueParent The parent node of value.
* @return Whether we should remove this assignment from the parse tree.
*/
private boolean processDefineAssignment(NodeTraversal t,
String name, Node value, Node valueParent) {
if (value == null || !NodeUtil.isValidDefineValue(value,
allDefines.keySet())) {
compiler.report(
t.makeError(value, INVALID_DEFINE_INIT_ERROR, name));
} else if (!isAssignAllowed()) {
compiler.report(
t.makeError(valueParent, NON_GLOBAL_DEFINE_INIT_ERROR, name));
} else {
DefineInfo info = allDefines.get(name);
if (info == null) {
// First declaration of this define.
info = new DefineInfo(value, valueParent);
allDefines.put(name, info);
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> visit all nodes but not traverse into function
* bodies.
*/
public abstract static class AbstractShallowCallback implements Callback {
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body.
return parent == null || parent.getType() != Token.FUNCTION ||
n == parent.getFirstChild();
}
}
/**
* Abstract callback to visit all structure and statement nodes but doesn't
* traverse into functions or expressions.
*/
public abstract static class AbstractShallowStatementCallback
implements Callback {
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return parent == null || NodeUtil.isControlStructure(parent)
|| NodeUtil.isStatementBlock(parent);
}
}
/**
* Abstract callback to visit a pruned set of nodes.
*
*/
public abstract static class AbstractNodeTypePruningCallback
implements Callback {
private final Set<Integer> nodeTypes;
private final boolean include;
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes) {
this(nodeTypes, true);
}
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include/exclude in the traversal
* @param include whether to include or exclude the nodes in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes,
boolean include) {
this.nodeTypes = nodeTypes;
this.include = include;
}
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return include == nodeTypes.contains(n.getType());
}
}
/**
* Creates a node traversal using the specified callback interface.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb) {
this(compiler, cb, new SyntacticScopeCreator(compiler));
}
/**
* Creates a node traversal using the specified callback interface
* and the scope creator.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb,
ScopeCreator scopeCreator) {
this.callback = cb;
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>fallthrough")
private void traverseBranch(Node n, Node parent) {
int type = n.getType();
if (type == Token.SCRIPT) {
sourceName = getSourceName(n);
}
curNode = n;
if (!callback.shouldTraverse(this, n, parent)) return;
switch (type) {
case Token.CATCH:
Preconditions.checkState(n.getChildCount() == 3);
Preconditions.checkState(n.getFirstChild().getType() == Token.NAME);
// the first child is the catch var and the third child
// is the code block
traverseBranch(n.getFirstChild(), n);
traverseBranch(n.getFirstChild().getNext().getNext(), n);
break;
case Token.FUNCTION:
traverseFunction(n, parent);
break;
default:
for (Node child = n.getFirstChild(); child != null; ) {
// child could be replaced, in which case our child node
// would no longer point to the true next
Node next = child.getNext();
traverseBranch(child, n);
child = next;
}
break;
}
curNode = n;
callback.visit(this, n, parent);
}
/**
* Traverses a function.
*/
private void traverseFunction(Node n, Node parent) {
Preconditions.checkState(n.getChildCount() == 3);
Preconditions.checkState(n.getType() == Token.FUNCTION);
final Node fnName = n.getFirstChild();
boolean isFunctionExpression = (parent != null)
&& NodeUtil.isFunctionExpression(n);
if (!isFunctionExpression) {
// Functions declarations are in the scope containing the declaration.
traverseBranch(fnName, n);
}
curNode = n;
pushScope(n);
if (isFunctionExpression) {
// Function expression names are only accessible within the function
// scope.
traverseBranch(fnName, n);
}
final Node args = fnName.getNext();
final Node body = args.getNext();
// Args
traverseBranch(args, n);
// Body
Preconditions.checkState(body.getNext() == null &&
body.getType() == Token.BLOCK);
traverseBranch(body, n);
popScope();
}
/** Examines the functions stack for
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>IN_EXTERNS_ERROR =
DiagnosticType.warning(
"JSC_NAME_REFERENCE_IN_EXTERNS",
"accessing name {0} in externs has no effect");
static final DiagnosticType INVALID_FUNCTION_DECL =
DiagnosticType.error("JSC_INVALID_FUNCTION_DECL",
"Syntax error: function declaration must have a name");
private CompilerInput synthesizedExternsInput = null;
private Node synthesizedExternsRoot = null;
private final AbstractCompiler compiler;
// Whether this is the post-processing sanity check.
private final boolean sanityCheck;
VarCheck(AbstractCompiler compiler) {
this(compiler, false);
}
VarCheck(AbstractCompiler compiler, boolean sanityCheck) {
this.compiler = compiler;
this.sanityCheck = sanityCheck;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, externs, new NameRefInExternsCheck());
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() != Token.NAME) {
return;
}
String varName = n.getString();
// Only a function can have an empty name.
if (varName.isEmpty()) {
Preconditions.checkState(NodeUtil.isFunction(parent));
// A function declaration with an empty name passes Rhino,
// but is supposed to be a syntax error according to the spec.
if (!NodeUtil.isFunctionExpression(parent)) {
t.report(n, INVALID_FUNCTION_DECL);
}
return;
}
// Check that the var has been declared.
Scope scope = t.getScope();
Scope.Var var = scope.getVar(varName);
if (var == null) {
if (NodeUtil.isFunctionExpression(parent)) {
// e.g. [ function foo() {} ], it's okay if "foo" isn't defined in the
// current scope.
} else {
t.report(n, UNDEFINED_VAR_ERROR, varName);
if (sanityCheck) {
throw new IllegalStateException("Unexpected variable " + varName);
} else {
// Create a
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> new variable in a synthetic script. This will prevent
// subsequent compiler passes from crashing.
Node nameNode = Node.newString(Token.NAME, varName);
getSynthesizedExternsRoot().addChildToBack(
new Node(Token.VAR, nameNode));
scope.getGlobalScope().declare(varName, nameNode,
null, getSynthesizedExternsInput());
}
}
return;
}
CompilerInput currInput = t.getInput();
CompilerInput varInput = var.input;
if (currInput == varInput || currInput == null || varInput == null) {
// The variable was defined in the same file. This is fine.
return;
}
// Check module dependencies.
JSModule currModule = currInput.getModule();
JSModule varModule = varInput.getModule();
JSModuleGraph moduleGraph = compiler.getModuleGraph();
if (varModule != currModule && varModule != null && currModule != null) {
if (moduleGraph.dependsOn(currModule, varModule)) {
// The module dependency was properly declared.
} else {
if (!sanityCheck && scope.isGlobal()) {
if (moduleGraph.dependsOn(varModule, currModule)) {
// The variable reference violates a declared module dependency.
t.report(n, VIOLATED_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
} else {
// The variable reference is between two modules that have no
// dependency relationship. This should probably be considered an
// error, but just issue a warning for now.
t.report(n, MISSING_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
}
} else {
t.report(n, STRICT_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
}
}
}
}
/**
* A check for name references in the externs inputs. These used to prevent
* a variable from getting renamed, but no longer have any effect.
*/
private class NameRefInExternsCheck extends AbstractPostOrderCallback {
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.NAME) {
switch
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> of the preciser scope to the next link.
* If there is no next link, returns the blind scope.
*/
protected FlowScope nextPreciserScopeKnowingConditionOutcome(Node condition,
FlowScope blindScope, boolean outcome) {
return nextLink != null ? nextLink.getPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome) : blindScope;
}
/**
* Returns the type of a node in the given scope if the node corresponds to a
* name whose type is capable of being refined.
* @return The current type of the node if it can be refined, null otherwise.
*/
JSType getTypeIfRefinable(Node node, FlowScope scope) {
switch (node.getType()) {
case Token.NAME:
StaticSlot<JSType> nameVar = scope.getSlot(node.getString());
if (nameVar != null) {
JSType nameVarType = nameVar.getType();
if (nameVarType == null) {
nameVarType = node.getJSType();
}
return nameVarType;
}
return null;
case Token.GETPROP:
String qualifiedName = node.getQualifiedName();
if (qualifiedName == null) {
return null;
}
StaticSlot<JSType> propVar = scope.getSlot(qualifiedName);
JSType propVarType = null;
if (propVar != null) {
propVarType = propVar.getType();
}
if (propVarType == null) {
propVarType = node.getJSType();
}
if (propVarType == null) {
propVarType = getNativeType(UNKNOWN_TYPE);
}
return propVarType;
}
return null;
}
/**
* Declares a refined type in {@code scope} for the name represented by
* {@code node}. It must be possible to refine the type of the given node in
* the given scope, as determined by {@link #getTypeIfRefinable}.
*/
protected void declareNameInScope(FlowScope scope, Node node, JSType type) {
switch (node.getType()) {
case Token.NAME:
scope.inferSlotType(node.getString(), type);
break;
case Token.GETPROP:
String
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>Root) {
Preconditions.checkState(scopeCreator == null);
Preconditions.checkState(topScope == null);
Preconditions.checkState(jsRoot.getParent() != null);
Node externsAndJsRoot = jsRoot.getParent();
scopeCreator = new MemoizedScopeCreator(new TypedScopeCreator(compiler));
topScope = scopeCreator.createScope(externsAndJsRoot, null);
TypeInferencePass inference = new TypeInferencePass(compiler,
reverseInterpreter, topScope, scopeCreator);
inference.process(externsRoot, jsRoot);
process(externsRoot, jsRoot);
return topScope;
}
public void check(Node node, boolean externs) {
Preconditions.checkNotNull(node);
NodeTraversal t = new NodeTraversal(compiler, this, scopeCreator);
inExterns = externs;
t.traverseWithScope(node, topScope);
if (externs) {
inferJSDocInfo.process(node, null);
} else {
inferJSDocInfo.process(null, node);
}
}
public boolean shouldTraverse(
NodeTraversal t, Node n, Node parent) {
JSDocInfo info;
switch (n.getType()) {
case Token.SCRIPT:
case Token.VAR:
// @notypecheck
info = n.getJSDocInfo();
if (info != null && info.isNoTypeCheck()) {
return false;
}
break;
case Token.FUNCTION:
// @notypecheck
info = n.getJSDocInfo();
info = (info == null) ? parent.getJSDocInfo() : info;
if (info != null && info.isNoTypeCheck()) {
return false;
}
// normal type checking
final TypeCheck outerThis = this;
final Scope outerScope = t.getScope();
final FunctionType functionType = (FunctionType) n.getJSType();
final String functionPrivateName = n.getFirstChild().getString();
if (functionPrivateName != null && functionPrivateName.length() > 0 &&
outerScope.isDeclared(functionPrivateName, false) &&
// Ideally, we would want to check whether the type in the scope
// differs from the type being defined, but then the extern
// re
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>declarations of built-in types generates spurious warnings.
!(outerScope.getVar(
functionPrivateName).getType() instanceof FunctionType)) {
t.report(n, FUNCTION_MASKS_VARIABLE, functionPrivateName);
}
// TODO(user): Only traverse the function's body. The function's
// name and arguments are traversed by the scope creator, and ideally
// should not be traversed by the type checker.
break;
}
return true;
}
/**
* This is the meat of the type checking. It is basically one big switch,
* with each case representing one type of parse tree node. The individual
* cases are usually pretty straightforward.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of the node n.
*/
public void visit(NodeTraversal t, Node n, Node parent) {
JSType childType;
JSType leftType, rightType;
Node left, right;
// To be explicitly set to false if the node is not typeable.
boolean typeable = true;
switch (n.getType()) {
case Token.NAME:
typeable = visitName(t, n, parent);
break;
case Token.LP:
// If this is under a FUNCTION node, it is a parameter list and can be
// ignored here.
if (parent.getType() != Token.FUNCTION) {
ensureTyped(t, n, getJSType(n.getFirstChild()));
} else {
typeable = false;
}
break;
case Token.COMMA:
ensureTyped(t, n, getJSType(n.getLastChild()));
break;
case Token.TRUE:
case Token.FALSE:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.THIS:
ensureTyped(t, n, t.getScope().getTypeOfThis());
break;
case Token.REF_SPECIAL:
ensureTyped(t, n);
break;
case Token.GET_REF:
ensureTyped(t, n, getJSType(n.getFirstChild()));
break;
case Token.NULL:
ensure
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>Typed(t, n, NULL_TYPE);
break;
case Token.NUMBER:
if (n.getParent().getType() != Token.OBJECTLIT) {
ensureTyped(t, n, NUMBER_TYPE);
} else {
typeable = false;
}
break;
case Token.ARRAYLIT:
ensureTyped(t, n, ARRAY_TYPE);
break;
case Token.STRING:
if (n.getParent().getType() != Token.OBJECTLIT) {
ensureTyped(t, n, STRING_TYPE);
} else {
typeable = false;
}
break;
case Token.REGEXP:
ensureTyped(t, n, REGEXP_TYPE);
break;
case Token.GETPROP:
visitGetProp(t, n, parent);
typeable = !(parent.getType() == Token.ASSIGN &&
parent.getFirstChild() == n);
break;
case Token.GETELEM:
visitGetElem(t, n);
// The type of GETELEM is always unknown, so no point counting that.
// If that unknown leaks elsewhere (say by an assignment to another
// variable), then it will be counted.
typeable = false;
break;
case Token.VAR:
visitVar(t, n);
typeable = false;
break;
case Token.NEW:
visitNew(t, n);
typeable = true;
break;
case Token.CALL:
visitCall(t, n);
typeable = !NodeUtil.isExpressionNode(parent);
break;
case Token.RETURN:
visitReturn(t, n);
typeable = false;
break;
case Token.DEC:
case Token.INC:
left = n.getFirstChild();
validator.expectNumber(
t, left, getJSType(left), "increment/decrement");
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.NOT:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.VOID:
ensureTyped(t, n, VOID_TYPE);
break;
case Token.TYPEOF:
ensureTyped(t, n, STRING_TYPE);
break;
case Token.BITNOT:
childType = getJSType(n.getFirstChild());
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> if (!childType.matchesInt32Context()) {
t.report(n, BIT_OPERATION, NodeUtil.opToStr(n.getType()),
childType.toString());
}
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.POS:
case Token.NEG:
left = n.getFirstChild();
validator.expectNumber(t, left, getJSType(left), "sign operator");
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.EQ:
case Token.NE: {
leftType = getJSType(n.getFirstChild());
rightType = getJSType(n.getLastChild());
JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined();
JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined();
TernaryValue result =
leftTypeRestricted.testForEquality(rightTypeRestricted);
if (result != TernaryValue.UNKNOWN) {
if (n.getType() == Token.NE) {
result = result.not();
}
t.report(n, DETERMINISTIC_TEST, leftType.toString(),
rightType.toString(), result.toString());
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
}
case Token.SHEQ:
case Token.SHNE: {
leftType = getJSType(n.getFirstChild());
rightType = getJSType(n.getLastChild());
JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined();
JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined();
if (!leftTypeRestricted.canTestForShallowEqualityWith(
rightTypeRestricted)) {
t.report(n, DETERMINISTIC_TEST_NO_RESULT, leftType.toString(),
rightType.toString());
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
}
case Token.LT:
case Token.LE:
case Token.GT:
case Token.GE:
leftType = getJSType(n.getFirstChild());
rightType = getJSType(n.getLastChild());
if (rightType.isNumber()) {
validator.expectNumber(
t, n, leftType, "left side of numeric comparison");
} else
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> case Token.ASSIGN_SUB:
case Token.ASSIGN_ADD:
case Token.ASSIGN_MUL:
case Token.LSH:
case Token.RSH:
case Token.URSH:
case Token.DIV:
case Token.MOD:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
case Token.SUB:
case Token.ADD:
case Token.MUL:
visitBinaryOperator(n.getType(), t, n);
break;
case Token.DELPROP:
if (!isReference(n.getFirstChild())) {
t.report(n, BAD_DELETE);
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.CASE:
JSType switchType = getJSType(parent.getFirstChild());
JSType caseType = getJSType(n.getFirstChild());
validator.expectSwitchMatchesCase(t, n, switchType, caseType);
typeable = false;
break;
case Token.WITH: {
Node child = n.getFirstChild();
childType = getJSType(child);
validator.expectObject(
t, child, childType, "with requires an object");
typeable = false;
break;
}
case Token.FUNCTION:
visitFunction(t, n);
break;
// These nodes have no interesting type behavior.
case Token.LABEL:
case Token.LABEL_NAME:
case Token.SWITCH:
case Token.BREAK:
case Token.CATCH:
case Token.TRY:
case Token.SCRIPT:
case Token.EXPR_RESULT:
case Token.BLOCK:
case Token.EMPTY:
case Token.DEFAULT:
case Token.CONTINUE:
case Token.DEBUGGER:
case Token.THROW:
typeable = false;
break;
// These nodes require data flow analysis.
case Token.DO:
case Token.FOR:
case Token.IF:
case Token.WHILE:
typeable = false;
break;
// These nodes are typed during the type inference.
case Token.AND:
case Token.HOOK:
case Token.OBJECTLIT:
case Token.OR:
if (n.getJSType() != null) { // If we didn't run type inference.
ensureTyped(t
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> perform checks in addition to the ones below
if (object.getType() == Token.GETPROP) {
JSType jsType = getJSType(object.getFirstChild());
if (jsType.isInterface() &&
object.getLastChild().getString().equals("prototype")) {
visitInterfaceGetprop(t, assign, object, property, lvalue, rvalue);
}
}
// /** @type ... */object.name = ...;
if (info != null && info.hasType()) {
visitAnnotatedAssignGetprop(t, assign,
info.getType().evaluate(t.getScope(), typeRegistry), object,
property, rvalue);
return;
}
// /** @enum ... */object.name = ...;
if (info != null && info.hasEnumParameterType()) {
checkEnumInitializer(
t, rvalue, info.getEnumParameterType().evaluate(
t.getScope(), typeRegistry));
return;
}
// object.prototype = ...;
if (property.equals("prototype")) {
if (objectJsType instanceof FunctionType) {
FunctionType functionType = (FunctionType) objectJsType;
if (functionType.isConstructor()) {
JSType rvalueType = rvalue.getJSType();
validator.expectObject(t, rvalue, rvalueType,
OVERRIDING_PROTOTYPE_WITH_NON_OBJECT);
}
} else {
// TODO(user): might want to flag that
}
return;
}
// object.prototype.property = ...;
if (object.getType() == Token.GETPROP) {
Node object2 = object.getFirstChild();
String property2 = NodeUtil.getStringValue(object.getLastChild());
if ("prototype".equals(property2)) {
JSType jsType = object2.getJSType();
if (jsType instanceof FunctionType) {
FunctionType functionType = (FunctionType) jsType;
if (functionType.isConstructor() || functionType.isInterface()) {
checkDeclaredPropertyInheritance(
t, assign, functionType, property, info, getJSType(rvalue));
}
} else {
// TODO(user): might want to flag that
}
return;
}
}
// object.property = ...;
ObjectType type = ObjectType.cast(
object
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>JsType.restrictByNotNullOrUndefined());
if (type != null) {
if (type.hasProperty(property) &&
!type.isPropertyTypeInferred(property) &&
!propertyIsImplicitCast(type, property)) {
validator.expectCanAssignToPropertyOf(
t, assign, getJSType(rvalue),
type.getPropertyType(property), object, property);
}
return;
}
} else if (lvalue.getType() == Token.NAME) {
// variable with inferred type case
JSType rvalueType = getJSType(assign.getLastChild());
Var var = t.getScope().getVar(lvalue.getString());
if (var != null) {
if (var.isTypeInferred()) {
return;
}
}
}
// fall through case
JSType leftType = getJSType(lvalue);
Node rightChild = assign.getLastChild();
JSType rightType = getJSType(rightChild);
if (validator.expectCanAssignTo(
t, assign, rightType, leftType, "assignment")) {
ensureTyped(t, assign, rightType);
} else {
ensureTyped(t, assign);
}
}
/**
* Returns true if any type in the chain has an implictCast annotation for
* the given property.
*/
private boolean propertyIsImplicitCast(ObjectType type, String prop) {
for (; type != null; type = type.getImplicitPrototype()) {
JSDocInfo docInfo = type.getOwnPropertyJSDocInfo(prop);
if (docInfo != null && docInfo.isImplicitCast()) {
return true;
}
}
return false;
}
/**
* Given a constructor type and a property name, check that the property has
* the JSDoc annotation @override iff the property is declared on a
* superclass. Several checks regarding inheritance correctness are also
* performed.
*/
private void checkDeclaredPropertyInheritance(
NodeTraversal t, Node n, FunctionType ctorType, String propertyName,
JSDocInfo info, JSType propertyType) {
// TODO(user): We're not 100% confident that type-checking works,
// so we return quietly if the unknown type is a superclass of this type.
// Remove this check
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> // property
compiler.report(t.makeError(n, reportMissingOverride,
HIDDEN_SUPERCLASS_PROPERTY, propertyName,
topInstanceType.toString()));
}
if (!declaredOverride) {
// there's no @override to check
return;
}
// @override is present and we have to check that it is ok
if (superClassHasProperty) {
// there is a superclass implementation
JSType superClassPropType =
superClass.getPrototype().getPropertyType(propertyName);
if (!propertyType.canAssignTo(superClassPropType)) {
compiler.report(
t.makeError(n, HIDDEN_SUPERCLASS_PROPERTY_MISMATCH,
propertyName, topInstanceType.toString(),
superClassPropType.toString(), propertyType.toString()));
}
} else if (!foundInterfaceProperty) {
// there is no superclass nor interface implementation
compiler.report(
t.makeError(n, UNKNOWN_OVERRIDE,
propertyName, ctorType.getInstanceType().toString()));
}
}
/**
* Visits an ASSIGN node for cases such as
* <pre>
* interface.property2.property = ...;
* </pre>
*/
private void visitInterfaceGetprop(NodeTraversal t, Node assign, Node object,
String property, Node lvalue, Node rvalue) {
JSType rvalueType = getJSType(rvalue);
String abstractMethodName =
compiler.getCodingConvention().getAbstractMethodName();
if (!rvalueType.isOrdinaryFunction() &&
!(rvalue.isQualifiedName() &&
rvalue.getQualifiedName().equals(abstractMethodName))) {
compiler.report(
t.makeError(object, INTERFACE_FUNCTION_MEMBERS_ONLY,
abstractMethodName));
}
if (assign.getLastChild().getType() == Token.FUNCTION
&& !NodeUtil.isEmptyBlock(assign.getLastChild().getLastChild())) {
compiler.report(
t.makeError(object, INTERFACE_FUNCTION_NOT_EMPTY,
abstractMethodName));
}
}
/**
* Visits an ASSIGN node for cases such as
* <pre>
* object.property = ...;
* </pre>
* that have an {@code @type} annotation.
*/
private void visitAnnotatedAssignGetprop(NodeTraversal t,
Node assign, JS
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>
* Determines whether this node is testing for the existence of a property.
* If true, we will not emit warnings about a missing property.
*
* @param getProp The GETPROP being tested.
*/
private boolean isPropertyTest(Node getProp) {
Node parent = getProp.getParent();
switch (parent.getType()) {
case Token.CALL:
return parent.getFirstChild() != getProp &&
compiler.getCodingConvention().isPropertyTestFunction(parent);
case Token.IF:
case Token.WHILE:
case Token.DO:
case Token.FOR:
return NodeUtil.getConditionExpression(parent) == getProp;
case Token.INSTANCEOF:
case Token.TYPEOF:
return true;
case Token.AND:
case Token.HOOK:
return parent.getFirstChild() == getProp;
}
return false;
}
/**
* Visits a GETELEM node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitGetElem(NodeTraversal t, Node n) {
Node left = n.getFirstChild();
Node right = n.getLastChild();
validator.expectIndexMatch(t, n, getJSType(left), getJSType(right));
ensureTyped(t, n);
}
/**
* Visits a VAR node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitVar(NodeTraversal t, Node n) {
// TODO(nicksantos): Fix this so that the doc info always shows up
// on the NAME node. We probably want to wait for the parser
// merge to fix this.
JSDocInfo varInfo = n.hasOneChild() ? n.getJSDocInfo() : null;
for (Node name : n.children()) {
Node value = name.getFirstChild();
// A null var would indicate a bug in the scope creation logic.
Var var = t.getScope().getVar(name.getString());
if
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> This is a misplaced return, but the real JS will fail to compile,
// so let it go.
if (function == null) {
return;
}
JSType jsType = getJSType(function);
if (jsType instanceof FunctionType) {
FunctionType functionType = (FunctionType) jsType;
JSType returnType = functionType.getReturnType();
// if no return type is specified, undefined must be returned
// (it's a void function)
if (returnType == null) {
returnType = getNativeType(VOID_TYPE);
}
// fetching the returned value's type
Node valueNode = n.getFirstChild();
JSType actualReturnType;
if (valueNode == null) {
actualReturnType = getNativeType(VOID_TYPE);
valueNode = n;
} else {
actualReturnType = getJSType(valueNode);
}
// verifying
validator.expectCanAssignTo(t, valueNode, actualReturnType, returnType,
"inconsistent return type");
}
}
/**
* This function unifies the type checking involved in the core binary
* operators and the corresponding assignment operators. The representation
* used internally is such that common code can handle both kinds of
* operators easily.
*
* @param op The operator.
* @param t The traversal object, needed to report errors.
* @param n The node being checked.
*/
private void visitBinaryOperator(int op, NodeTraversal t, Node n) {
Node left = n.getFirstChild();
JSType leftType = getJSType(left);
Node right = n.getLastChild();
JSType rightType = getJSType(right);
switch (op) {
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.LSH:
case Token.RSH:
case Token.ASSIGN_URSH:
case Token.URSH:
if (!leftType.matchesInt32Context()) {
t.report(left, BIT_OPERATION,
NodeUtil.opToStr(n.getType()), leftType.toString());
}
if (!rightType.matchesUint32Context()) {
t.report(right, BIT_OPERATION,
NodeUtil.opToStr(n.getType()), rightType.toString());
}
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> must be assignable to the enum's primitive type
validator.expectCanAssignTo(t, value, getJSType(value), primitiveType,
"element type must match enum's type");
// advancing twice
value = value.getNext();
value = (value == null) ? null : value.getNext();
}
} else if (value.getJSType() instanceof EnumType) {
// TODO(user): Remove the instanceof check in favor
// of a type.isEnumType() predicate. Currently, not all enum types are
// implemented by the EnumClass, e.g. the unknown type and the any
// type. The types need to be defined by interfaces such that an
// implementation can implement multiple types interface.
EnumType valueEnumType = (EnumType) value.getJSType();
JSType valueEnumPrimitiveType =
valueEnumType.getElementsType().getPrimitiveType();
validator.expectCanAssignTo(t, value, valueEnumPrimitiveType,
primitiveType, "incompatible enum element types");
} else {
// The error condition is handled in TypedScopeCreator.
}
}
/**
* This predicate is used to determine if the node represents an expression
* that is a Reference according to JavaScript definitions.
*
* @param n The node being checked.
* @return true if the sub-tree n is a reference, false otherwise.
*/
private static boolean isReference(Node n) {
switch (n.getType()) {
case Token.GETELEM:
case Token.GETPROP:
case Token.NAME:
return true;
default:
return false;
}
}
/**
* This method gets the JSType from the Node argument and verifies that it is
* present.
*/
private JSType getJSType(Node n) {
JSType jsType = n.getJSType();
if (jsType == null) {
// TODO(nicksantos): This branch indicates a compiler bug, not worthy of
// halting the compilation but we should log this and analyze to track
// down why it happens. This is not critical and will be resolved over
// time as the type checker is extended.
return getNativeType(UNKNOWN_TYPE);
} else {
return jsType;
}
}
/**
* Gets the type of the node or
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.Node;
/**
* Look for references to the global RegExp object that would cause
* regular expressions to be unoptimizable.
*
* @author johnlenz@google.com (John Lenz)
*/
class CheckRegExp extends AbstractPostOrderCallback implements CompilerPass {
static final DiagnosticType REGEXP_REFERENCE =
DiagnosticType.warning("JSC_REGEXP_REFERENCE",
"References to the global RegExp object prevents " +
"optimization of regular expressions.");
private final AbstractCompiler compiler;
private boolean globalRegExpPropertiesUsed = false;
public boolean isGlobalRegExpPropertiesUsed() {
return globalRegExpPropertiesUsed;
}
public CheckRegExp(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isReferenceName(n)) {
String name = n.getString();
if (name.equals("RegExp") && t.getScope().getVar(name) == null) {
int parentType = parent.getType();
boolean first = (n == parent.getFirstChild());
if (!((parentType == Token.NEW && first)
|| (parentType == Token.CALL && first)
|| (parentType == Token.INSTANCEOF && !
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>OverviewInfo.setLicense(irNode.getJSDocInfo().getLicense());
}
irNode.setJSDocInfo(fileOverviewInfo);
}
}
return irNode;
}
private Node transformBlock(AstNode node) {
Node irNode = transform(node);
if (irNode.getType() != Token.BLOCK) {
if (irNode.getType() == Token.EMPTY) {
irNode.setType(Token.BLOCK);
irNode.setWasEmptyNode(true);
} else {
Node newBlock = new Node(Token.BLOCK, irNode,
irNode.getLineno(), irNode.getCharno());
irNode = newBlock;
}
}
return irNode;
}
private Node transform(AstNode node) {
String jsDoc = node.getJsDoc();
NodeWithJsDoc nodeWithJsDoc = null;
if (jsDoc != null) {
nodeWithJsDoc = new NodeWithJsDoc();
nodesWithJsDoc.put(jsDoc, nodeWithJsDoc);
}
Node irNode = justTransform(node);
if (nodeWithJsDoc != null) {
nodeWithJsDoc.node = irNode;
}
// If we have a named function, set the position to that of the name.
if (irNode.getType() == Token.FUNCTION &&
irNode.getFirstChild().getLineno() != -1) {
irNode.setLineno(irNode.getFirstChild().getLineno());
irNode.setCharno(irNode.getFirstChild().getCharno());
} else {
if (irNode.getLineno() == -1) {
// If we didn't already set the line, then set it now. This avoids
// cases like ParenthesizedExpression where we just return a previous
// node, but don't want the new node to get its parent's line number.
int lineno = node.getLineno();
irNode.setLineno(lineno);
int charno = position2charno(node.getAbsolutePosition());
irNode.setCharno(charno);
}
}
return irNode;
}
/**
* Creates a JsDocInfoParser and parses the JsDoc string.
*
* Used both for handling individual
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>Index - 1;
}
}
private Node justTransform(AstNode node) {
return transformDispatcher.process(node);
}
private class TransformDispatcher extends TypeSafeDispatcher<Node> {
private Node processGeneric(
com.google.javascript.jscomp.mozilla.rhino.Node n) {
Node node = new Node(transformTokenType(n.getType()));
for (com.google.javascript.jscomp.mozilla.rhino.Node child : n) {
node.addChildToBack(transform((AstNode)child));
}
return node;
}
/**
* Transforms the given node and then sets its type to Token.STRING if it
* was Token.NAME. If its type was already Token.STRING, then quotes it.
* Used for properties, as the old AST uses String tokens, while the new one
* uses Name tokens for unquoted strings. For example, in
* var o = {'a' : 1, b: 2};
* the string 'a' is quoted, while the name b is turned into a string, but
* unquoted.
*/
private Node transformAsString(AstNode n) {
Node ret = transform(n);
if (ret.getType() == Token.STRING) {
ret.putBooleanProp(Node.QUOTED_PROP, true);
} else if (ret.getType() == Token.NAME) {
ret.setType(Token.STRING);
}
return ret;
}
@Override
Node processArrayLiteral(ArrayLiteral literalNode) {
if (literalNode.isDestructuring()) {
reportDestructuringAssign(literalNode);
}
Node node = new Node(Token.ARRAYLIT);
int skipCount = 0;
for (AstNode child : literalNode.getElements()) {
Node c = transform(child);
if (c.getType() == Token.EMPTY) {
skipCount++;
}
node.addChildToBack(c);
}
if (skipCount > 0) {
int[] skipIndexes = new int[skipCount];
int i = 0;
int j = 0;
for (Node child : node.children()) {
if (child.getType() == Token.EMPTY) {
node.removeChild(child);
skipIndexes[j]
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> = i;
j++;
}
i++;
}
node.putProp(Node.SKIP_INDEXES_PROP, skipIndexes);
}
return node;
}
@Override
Node processAssignment(Assignment assignmentNode) {
return processInfixExpression(assignmentNode);
}
@Override
Node processAstRoot(AstRoot rootNode) {
Node node = new Node(Token.SCRIPT);
for (com.google.javascript.jscomp.mozilla.rhino.Node child : rootNode) {
node.addChildToBack(transform((AstNode)child));
}
parseDirectives(node);
return node;
}
/**
* Parse the directives, encode them in the AST, and remove their nodes.
*
* For information on ES5 directives, see section 14.1 of
* Ecma-262, Edition 5.
*
* It would be nice if Rhino would eventually take care of this for
* us, but right now their directive-processing is a one-off.
*/
private void parseDirectives(Node node) {
// Remove all the directives, and encode them in the AST.
Set<String> directives = null;
while (isDirective(node.getFirstChild())) {
String directive = node.removeFirstChild().getFirstChild().getString();
if (directives == null) {
directives = Sets.newHashSet(directive);
} else {
directives.add(directive);
}
}
if (directives != null) {
node.setDirectives(directives);
}
}
private boolean isDirective(Node n) {
if (n == null) return false;
int nType = n.getType();
return (nType == Token.EXPR_RESULT || nType == Token.EXPR_VOID) &&
n.getFirstChild().getType() == Token.STRING &&
ALLOWED_DIRECTIVES.contains(n.getFirstChild().getString());
}
@Override
Node processBlock(Block blockNode) {
return processGeneric(blockNode);
}
@Override
Node processBreakStatement(BreakStatement statementNode) {
Node node = new Node(Token.BREAK);
if (statementNode.getBreakLabel() != null) {
Node labelName = transform(statementNode.getBreakLabel());
// Change the NAME
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> Add the offset
// to the operator to get the correct character number.
n.setCharno(position2charno(exprNode.getAbsolutePosition() +
exprNode.getOperatorPosition()));
return n;
}
@Override
Node processKeywordLiteral(KeywordLiteral literalNode) {
return new Node(transformTokenType(literalNode.getType()));
}
@Override
Node processLabel(Label labelNode) {
return Node.newString(Token.LABEL_NAME, labelNode.getName());
}
@Override
Node processLabeledStatement(LabeledStatement statementNode) {
Node node = new Node(Token.LABEL);
Node prev = null;
Node cur = node;
for (Label label : statementNode.getLabels()) {
if (prev != null) {
prev.addChildToBack(cur);
}
cur.addChildToBack(transform(label));
cur.setLineno(label.getLineno());
int clauseAbsolutePosition =
position2charno(label.getAbsolutePosition());
cur.setCharno(clauseAbsolutePosition);
prev = cur;
cur = new Node(Token.LABEL);
}
prev.addChildToBack(transform(statementNode.getStatement()));
return node;
}
@Override
Node processName(Name nameNode) {
return Node.newString(Token.NAME, nameNode.getIdentifier());
}
@Override
Node processNewExpression(NewExpression exprNode) {
return processFunctionCall(exprNode);
}
@Override
Node processNumberLiteral(NumberLiteral literalNode) {
Node newNode = Node.newNumber(literalNode.getNumber());
return newNode;
}
@Override
Node processObjectLiteral(ObjectLiteral literalNode) {
if (literalNode.isDestructuring()) {
reportDestructuringAssign(literalNode);
}
Node node = new Node(Token.OBJECTLIT);
for (ObjectProperty el : literalNode.getElements()) {
if (el.isGetter()) {
reportGetter(el);
} else if (el.isSetter()) {
reportSetter(el);
} else {
node.addChildToBack(transformAsString(el.getLeft()));
node.addChildToBack(transform(el.getRight()));
}
}
return node;
}
@Override
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> Node processObjectProperty(ObjectProperty propertyNode) {
return processInfixExpression(propertyNode);
}
@Override
Node processParenthesizedExpression(ParenthesizedExpression exprNode) {
Node node = transform(exprNode.getExpression());
node.putProp(Node.PARENTHESIZED_PROP, Boolean.TRUE);
return node;
}
@Override
Node processPropertyGet(PropertyGet getNode) {
return new Node(
Token.GETPROP,
transform(getNode.getTarget()),
transformAsString(getNode.getProperty()));
}
@Override
Node processRegExpLiteral(RegExpLiteral literalNode) {
Node literalStringNode = Node.newString(literalNode.getValue());
// assume it's on the same line.
literalStringNode.setLineno(literalNode.getLineno());
Node node = new Node(Token.REGEXP, literalStringNode);
String flags = literalNode.getFlags();
if (flags != null && !flags.isEmpty()) {
Node flagsNode = Node.newString(flags);
// Assume the flags are on the same line as the literal node.
flagsNode.setLineno(literalNode.getLineno());
node.addChildToBack(flagsNode);
}
return node;
}
@Override
Node processReturnStatement(ReturnStatement statementNode) {
Node node = new Node(Token.RETURN);
if (statementNode.getReturnValue() != null) {
node.addChildToBack(transform(statementNode.getReturnValue()));
}
return node;
}
@Override
Node processScope(Scope scopeNode) {
return processGeneric(scopeNode);
}
@Override
Node processStringLiteral(StringLiteral literalNode) {
Node n = Node.newString(literalNode.getValue());
return n;
}
@Override
Node processSwitchCase(SwitchCase caseNode) {
Node node;
if (caseNode.isDefault()) {
node = new Node(Token.DEFAULT);
} else {
AstNode expr = caseNode.getExpression();
node = new Node(Token.CASE, transform(expr));
}
Node block = new Node(Token.BLOCK);
block.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true);
block.setLineno(caseNode.getLineno());
block.
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>setCharno(position2charno(caseNode.getAbsolutePosition()));
if (caseNode.getStatements() != null) {
for (AstNode child : caseNode.getStatements()) {
block.addChildToBack(transform(child));
}
}
node.addChildToBack(block);
return node;
}
@Override
Node processSwitchStatement(SwitchStatement statementNode) {
Node node = new Node(Token.SWITCH,
transform(statementNode.getExpression()));
for (AstNode child : statementNode.getCases()) {
node.addChildToBack(transform(child));
}
return node;
}
@Override
Node processThrowStatement(ThrowStatement statementNode) {
return new Node(Token.THROW,
transform(statementNode.getExpression()));
}
@Override
Node processTryStatement(TryStatement statementNode) {
Node node = new Node(Token.TRY,
transformBlock(statementNode.getTryBlock()));
Node block = new Node(Token.BLOCK);
boolean lineSet = false;
for (CatchClause cc : statementNode.getCatchClauses()) {
// Mark the enclosing block at the same line as the first catch
// clause.
if (lineSet == false) {
block.setLineno(cc.getLineno());
lineSet = true;
}
block.addChildToBack(transform(cc));
}
node.addChildToBack(block);
AstNode finallyBlock = statementNode.getFinallyBlock();
if (finallyBlock != null) {
node.addChildToBack(transformBlock(finallyBlock));
}
// If we didn't set the line on the catch clause, then
// we've got an empty catch clause. Set its line to be the same
// as the finally block (to match Old Rhino's behavior.)
if ((lineSet == false) && (finallyBlock != null)) {
block.setLineno(finallyBlock.getLineno());
}
return node;
}
@Override
Node processUnaryExpression(UnaryExpression exprNode) {
Node node = new Node(transformTokenType(exprNode.getType()),
transform(exprNode.getOperand()));
if (exprNode.isPostfix()) {
node.putBooleanProp(Node.INCRDECR_PROP, true);
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>.rhino.Token.NAME:
return Token.NAME;
case com.google.javascript.jscomp.mozilla.rhino.Token.NUMBER:
return Token.NUMBER;
case com.google.javascript.jscomp.mozilla.rhino.Token.STRING:
return Token.STRING;
case com.google.javascript.jscomp.mozilla.rhino.Token.NULL:
return Token.NULL;
case com.google.javascript.jscomp.mozilla.rhino.Token.THIS:
return Token.THIS;
case com.google.javascript.jscomp.mozilla.rhino.Token.FALSE:
return Token.FALSE;
case com.google.javascript.jscomp.mozilla.rhino.Token.TRUE:
return Token.TRUE;
case com.google.javascript.jscomp.mozilla.rhino.Token.SHEQ:
return Token.SHEQ;
case com.google.javascript.jscomp.mozilla.rhino.Token.SHNE:
return Token.SHNE;
case com.google.javascript.jscomp.mozilla.rhino.Token.REGEXP:
return Token.REGEXP;
case com.google.javascript.jscomp.mozilla.rhino.Token.BINDNAME:
return Token.BINDNAME;
case com.google.javascript.jscomp.mozilla.rhino.Token.THROW:
return Token.THROW;
case com.google.javascript.jscomp.mozilla.rhino.Token.RETHROW:
return Token.RETHROW;
case com.google.javascript.jscomp.mozilla.rhino.Token.IN:
return Token.IN;
case com.google.javascript.jscomp.mozilla.rhino.Token.INSTANCEOF:
return Token.INSTANCEOF;
case com.google.javascript.jscomp.mozilla.rhino.Token.LOCAL_LOAD:
return Token.LOCAL_LOAD;
case com.google.javascript.jscomp.mozilla.rhino.Token.GETVAR:
return Token.GETVAR;
case com.google.javascript.jscomp.mozilla.rhino.Token.SETVAR:
return Token.SETVAR;
case com.google.javascript.jscomp.mozilla.rhino.Token.CATCH_SCOPE:
return Token.CATCH_SCOPE;
case com
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>google.javascript.jscomp.mozilla.rhino.Token.SWITCH:
return Token.SWITCH;
case com.google.javascript.jscomp.mozilla.rhino.Token.CASE:
return Token.CASE;
case com.google.javascript.jscomp.mozilla.rhino.Token.DEFAULT:
return Token.DEFAULT;
case com.google.javascript.jscomp.mozilla.rhino.Token.WHILE:
return Token.WHILE;
case com.google.javascript.jscomp.mozilla.rhino.Token.DO:
return Token.DO;
case com.google.javascript.jscomp.mozilla.rhino.Token.FOR:
return Token.FOR;
case com.google.javascript.jscomp.mozilla.rhino.Token.BREAK:
return Token.BREAK;
case com.google.javascript.jscomp.mozilla.rhino.Token.CONTINUE:
return Token.CONTINUE;
case com.google.javascript.jscomp.mozilla.rhino.Token.VAR:
return Token.VAR;
case com.google.javascript.jscomp.mozilla.rhino.Token.WITH:
return Token.WITH;
case com.google.javascript.jscomp.mozilla.rhino.Token.CATCH:
return Token.CATCH;
case com.google.javascript.jscomp.mozilla.rhino.Token.FINALLY:
return Token.FINALLY;
case com.google.javascript.jscomp.mozilla.rhino.Token.VOID:
return Token.VOID;
case com.google.javascript.jscomp.mozilla.rhino.Token.RESERVED:
return Token.RESERVED;
case com.google.javascript.jscomp.mozilla.rhino.Token.EMPTY:
return Token.EMPTY;
case com.google.javascript.jscomp.mozilla.rhino.Token.BLOCK:
return Token.BLOCK;
case com.google.javascript.jscomp.mozilla.rhino.Token.LABEL:
return Token.LABEL;
case com.google.javascript.jscomp.mozilla.rhino.Token.TARGET:
return Token.TARGET;
case com.google.javascript.jscomp.mozilla.rhino.Token.LOOP:
return Token.LOOP;
case com.google.javascript.jscomp.mozilla.rhino
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>.Token.EXPR_VOID:
case com.google.javascript.jscomp.mozilla.rhino.Token.EXPR_RESULT:
return Token.EXPR_RESULT;
case com.google.javascript.jscomp.mozilla.rhino.Token.JSR:
return Token.JSR;
case com.google.javascript.jscomp.mozilla.rhino.Token.SCRIPT:
return Token.SCRIPT;
case com.google.javascript.jscomp.mozilla.rhino.Token.TYPEOFNAME:
return Token.TYPEOFNAME;
case com.google.javascript.jscomp.mozilla.rhino.Token.USE_STACK:
return Token.USE_STACK;
case com.google.javascript.jscomp.mozilla.rhino.Token.SETPROP_OP:
return Token.SETPROP_OP;
case com.google.javascript.jscomp.mozilla.rhino.Token.SETELEM_OP:
return Token.SETELEM_OP;
case com.google.javascript.jscomp.mozilla.rhino.Token.LOCAL_BLOCK:
return Token.LOCAL_BLOCK;
case com.google.javascript.jscomp.mozilla.rhino.Token.SET_REF_OP:
return Token.SET_REF_OP;
case com.google.javascript.jscomp.mozilla.rhino.Token.DOTDOT:
return Token.DOTDOT;
case com.google.javascript.jscomp.mozilla.rhino.Token.COLONCOLON:
return Token.COLONCOLON;
case com.google.javascript.jscomp.mozilla.rhino.Token.XML:
return Token.XML;
case com.google.javascript.jscomp.mozilla.rhino.Token.DOTQUERY:
return Token.DOTQUERY;
case com.google.javascript.jscomp.mozilla.rhino.Token.XMLATTR:
return Token.XMLATTR;
case com.google.javascript.jscomp.mozilla.rhino.Token.XMLEND:
return Token.XMLEND;
case com.google.javascript.jscomp.mozilla.rhino.Token.TO_OBJECT:
return Token.TO_OBJECT;
case com.google.javascript.jscomp.mozilla.rhino.Token.TO_DOUBLE:
return Token.TO_
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> return getPassConfig().getScopeCreator();
}
@Override
public Scope getTopScope() {
return getPassConfig().getTopScope();
}
@Override
public ReverseAbstractInterpreter getReverseAbstractInterpreter() {
if (abstractInterpreter == null) {
ChainableReverseAbstractInterpreter interpreter =
new SemanticReverseAbstractInterpreter(
getCodingConvention(), getTypeRegistry());
if (options.closurePass) {
interpreter = new ClosureReverseAbstractInterpreter(
getCodingConvention(), getTypeRegistry())
.append(interpreter).getFirst();
}
abstractInterpreter = interpreter;
}
return abstractInterpreter;
}
@Override
TypeValidator getTypeValidator() {
if (typeValidator == null) {
typeValidator = new TypeValidator(this);
}
return typeValidator;
}
//------------------------------------------------------------------------
// Parsing
//------------------------------------------------------------------------
/**
* Parses the externs and main inputs.
*
* @return A synthetic root node whose two children are the externs root
* and the main root
*/
Node parseInputs() {
boolean devMode = options.devMode != DevMode.OFF;
// If old roots exist (we are parsing a second time), detach each of the
// individual file parse trees.
if (externsRoot != null) {
externsRoot.detachChildren();
}
if (jsRoot != null) {
jsRoot.detachChildren();
}
// Parse main js sources.
jsRoot = new Node(Token.BLOCK);
jsRoot.setIsSyntheticBlock(true);
if (options.tracer.isOn()) {
tracker = new PerformanceTracker(jsRoot,
options.tracer == TracerMode.ALL);
addChangeHandler(tracker.getCodeChangeHandler());
}
Tracer tracer = newTracer("parseInputs");
try {
// Parse externs sources.
externsRoot = new Node(Token.BLOCK);
externsRoot.setIsSyntheticBlock(true);
for (CompilerInput input : externs) {
Node n = input.getAstRoot(this);
if (hasErrors()) {
return null;
}
externsRoot.addChildToBack(n);
}
// Check if the sources need to be re-ordered.
if (options.manageClosureDependencies) {
for (CompilerInput input : inputs) {
input.setCompiler
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> sia =
new SourceInformationAnnotator(input.getName());
NodeTraversal.traverse(this, n, sia);
}
jsRoot.addChildToBack(n);
}
externAndJsRoot = new Node(Token.BLOCK, externsRoot, jsRoot);
externAndJsRoot.setIsSyntheticBlock(true);
return externAndJsRoot;
} finally {
stopTracer(tracer, "parseInputs");
}
}
public Node parse(JSSourceFile file) {
initCompilerOptionsIfTesting();
addToDebugLog("Parsing: " + file.getName());
return new JsAst(file).getAstRoot(this);
}
@Override
Node parseSyntheticCode(String js) {
CompilerInput input = new CompilerInput(
JSSourceFile.fromCode(" [synthetic] ", js));
inputsByName.put(input.getName(), input);
return input.getAstRoot(this);
}
void initCompilerOptionsIfTesting() {
if (options == null) {
// initialization for tests that don't initialize the compiler
// by the normal mechanisms.
initOptions(new CompilerOptions());
}
}
@Override
Node parseSyntheticCode(String fileName, String js) {
initCompilerOptionsIfTesting();
return parse(JSSourceFile.fromCode(fileName, js));
}
Node parseTestCode(String js) {
initCompilerOptionsIfTesting();
CompilerInput input = new CompilerInput(
JSSourceFile.fromCode(" [testcode] ", js));
if (inputsByName == null) {
inputsByName = Maps.newHashMap();
}
inputsByName.put(input.getName(), input);
return input.getAstRoot(this);
}
@Override
ErrorReporter getDefaultErrorReporter() {
return defaultErrorReporter;
}
//------------------------------------------------------------------------
// Convert back to source code
//------------------------------------------------------------------------
/**
* Converts the main parse tree back to js code.
*/
public String toSource() {
return runInCompilerThread(new Callable<String>() {
public String call() throws Exception {
Tracer tracer = newTracer("toSource");
try {
CodeBuilder cb = new CodeBuilder();
if (jsRoot != null) {
int i = 0;
for (Node scriptNode =
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>Inputs = inputs.size();
if (numInputs == 0) {
return new String[0];
}
String[] sources = new String[numInputs];
CodeBuilder cb = new CodeBuilder();
for (int i = 0; i < numInputs; i++) {
Node scriptNode = inputs.get(i).getAstRoot(Compiler.this);
if (scriptNode == null) {
throw new IllegalArgumentException(
"Bad module input: " + inputs.get(i).getName());
}
cb.reset();
toSource(cb, i, scriptNode);
sources[i] = cb.toString();
}
return sources;
}
});
}
/**
* Writes out js code from a root node. If printing input delimiters, this
* method will attach a comment to the start of the text indicating which
* input the output derived from. If there were any preserve annotations
* within the root's source, they will also be printed in a block comment
* at the beginning of the output.
*/
public void toSource(final CodeBuilder cb,
final int inputSeqNum,
final Node root) {
runInCompilerThread(new Callable<Void>() {
public Void call() throws Exception {
if (options.printInputDelimiter) {
if ((cb.getLength() > 0) && !cb.endsWith("\n")) {
cb.append("\n"); // Make sure that the label starts on a new line
}
Preconditions.checkState(root.getType() == Token.SCRIPT);
String delimiter = options.inputDelimiter;
String sourceName = (String)root.getProp(Node.SOURCENAME_PROP);
Preconditions.checkState(sourceName != null);
Preconditions.checkState(!sourceName.isEmpty());
delimiter = delimiter.replaceAll("%name%", sourceName)
.replaceAll("%num%", String.valueOf(inputSeqNum));
cb.append(delimiter)
.append("\n");
}
if (root.getJSDocInfo() != null &&
root.getJSDocInfo().getLicense() != null) {
cb.append("/*\n")
.append(root.getJSDocInfo().getLicense())
.append("*/\n");
}
// If there is a valid source map, then indicate to
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> {
try {
parse(compiler, sourceFile.getName(), sourceFile.getCode());
} catch (IOException e) {
compiler.report(
JSError.make(AbstractCompiler.READ_ERROR, sourceFile.getName()));
}
}
private void parse(AbstractCompiler compiler, String sourceName,
String sourceStr) {
try {
logger_.fine("Parsing: " + sourceName);
root = ParserRunner.parse(sourceName, sourceStr,
compiler.getParserConfig(),
compiler.getDefaultErrorReporter(),
logger_);
} catch (IOException e) {
compiler.report(JSError.make(AbstractCompiler.READ_ERROR, sourceName));
}
if (root == null || compiler.hasHaltingErrors()) {
// There was a parse error or IOException, so use a dummy block.
root = new Node(Token.BLOCK);
} else {
compiler.prepareAst(root);
}
// Set the source name so that the compiler passes can track
// the source file and module.
root.putProp(Node.SOURCENAME_PROP, sourceName);
}
}
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>
AbstractCompiler compiler, RedeclarationHandler redeclarationHandler) {
this.compiler = compiler;
this.redeclarationHandler = redeclarationHandler;
}
public Scope createScope(Node n, Scope parent) {
sourceName = null;
if (parent == null) {
scope = new Scope(n, compiler);
} else {
scope = new Scope(parent, n);
}
scanRoot(n, parent);
sourceName = null;
Scope returnedScope = scope;
scope = null;
return returnedScope;
}
private void scanRoot(Node n, Scope parent) {
if (n.getType() == Token.FUNCTION) {
sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
final Node fnNameNode = n.getFirstChild();
final Node args = fnNameNode.getNext();
final Node body = args.getNext();
// Bleed the function name into the scope, if it hasn't
// been declared in the outer scope.
String fnName = fnNameNode.getString();
if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) {
declareVar(fnName, fnNameNode, n, null, null, n);
}
// Args: Declare function variables
Preconditions.checkState(args.getType() == Token.LP);
for (Node a = args.getFirstChild(); a != null;
a = a.getNext()) {
Preconditions.checkState(a.getType() == Token.NAME);
declareVar(a.getString(), a, args, n, null, n);
}
// Body
scanVars(body, n);
} else {
// It's the global block
Preconditions.checkState(scope.getParent() == null);
scanVars(n, null);
}
}
/**
* Scans and gather variables declarations under a Node
*/
private void scanVars(Node n, Node parent) {
switch (n.getType()) {
case Token.VAR:
// Declare all variables. e.g. var x = 1, y, z;
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
Preconditions.checkState(child.getType() == Token.NAME);
String name = child.getString
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>();
declareVar(name, child, n, parent, null, n);
child = next;
}
return;
case Token.FUNCTION:
if (NodeUtil.isFunctionExpression(n)) {
return;
}
String fnName = n.getFirstChild().getString();
if (fnName.isEmpty()) {
// This is invalid, but allow it so the checks can catch it.
return;
}
declareVar(fnName, n.getFirstChild(), n, parent, null, n);
return; // should not examine function's children
case Token.CATCH:
Preconditions.checkState(n.getChildCount() == 3);
Preconditions.checkState(n.getFirstChild().getType() == Token.NAME);
// the first child is the catch var and the third child
// is the code block
final Node var = n.getFirstChild();
final Node block = var.getNext().getNext();
declareVar(var.getString(), var, n, parent, null, n);
scanVars(block, n);
return; // only one child to scan
case Token.SCRIPT:
sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
break;
}
// Variables can only occur in statement-level nodes, so
// we only need to traverse children in a couple special cases.
if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) {
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
scanVars(child, n);
child = next;
}
}
}
/**
* Interface for injectable duplicate handling.
*/
interface RedeclarationHandler {
void onRedeclaration(
Scope s, String name,
Node n, Node parent, Node gramps, Node nodeWithLineNumber);
}
/**
* The default handler for duplicate declarations.
*/
private class DefaultRedeclarationHandler implements RedeclarationHandler {
public void onRedeclaration(
Scope s, String name,
Node n, Node parent, Node gramps, Node nodeWithLineNumber) {
// Don't allow multiple variables to be declared at the top level scope
if (scope.isGlobal()) {
Scope.Var origVar = scope.getVar(name);
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>
Node origParent = origVar.getParentNode();
if (origParent.getType() == Token.CATCH &&
parent.getType() == Token.CATCH) {
// Okay, both are 'catch(x)' variables.
return;
}
boolean allowDupe = false;
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
info = parent.getJSDocInfo();
}
allowDupe =
info != null && info.getSuppressions().contains("duplicate");
if (!allowDupe) {
compiler.report(
JSError.make(sourceName, nodeWithLineNumber,
VAR_MULTIPLY_DECLARED_ERROR,
name,
(origVar.input != null
? origVar.input.getName()
: "??")));
}
} else if (name.equals(ARGUMENTS) && !NodeUtil.isVarDeclaration(n)) {
// Disallow shadowing "arguments" as we can't handle with our current
// scope modeling.
compiler.report(
JSError.make(sourceName, nodeWithLineNumber,
VAR_ARGUMENTS_SHADOWED_ERROR));
}
}
}
/**
* Declares a variable.
*
* @param name The variable name
* @param n The node corresponding to the variable name (usually a NAME node)
* @param parent The parent node of {@code n}
* @param gramps The parent node of {@code parent}
* @param declaredType The variable's type, according to JSDoc
* @param nodeWithLineNumber The node to use to access the line number of
* the variable declaration, if needed
*/
private void declareVar(String name, Node n, Node parent,
Node gramps, JSType declaredType,
Node nodeWithLineNumber) {
if (scope.isDeclared(name, false)
|| (scope.isLocal() && name.equals(ARGUMENTS))) {
redeclarationHandler.onRedeclaration(
scope, name, n, parent, gramps, nodeWithLineNumber);
} else {
scope.declare(name, n, declaredType, compiler.getInput(sourceName));
}
}
}
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.ObjectType;
import java.nio.charset.Charset;
import java.util.Set;
/**
* A code generator that outputs type annotations for functions and
* constructors.
*
*/
class TypedCodeGenerator extends CodeGenerator {
TypedCodeGenerator(CodeConsumer consumer, Charset outputCharset) {
super(consumer, outputCharset);
}
@Override
void add(Node n, Context context) {
Node parent = n.getParent();
if (parent != null
&& (parent.getType() == Token.BLOCK
|| parent.getType() == Token.SCRIPT)) {
if (n.getType() == Token.FUNCTION) {
add(getFunctionAnnotation(n));
} else if (n.getType() == Token.EXPR_RESULT
&& n.getFirstChild().getType() == Token.ASSIGN) {
Node rhs = n.getFirstChild().getLastChild();
add(getTypeAnnotation(rhs));
} else if (n.getType() == Token.VAR
&& n.getFirstChild().getFirstChild() != null
&& n.getFirstChild().getFirstChild().getType() == Token.FUNCTION) {
add(getFunctionAnnotation(n.getFirstChild().getFirstChild
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>()));
}
}
super.add(n, context);
}
private String getTypeAnnotation(Node node) {
JSType type = node.getJSType();
if (type instanceof FunctionType) {
return getFunctionAnnotation(node);
} else if (type != null && !type.isUnknownType()
&& !type.isEmptyType() && !type.isVoidType() &&
!type.isFunctionPrototypeType()) {
return "/** @type {" + node.getJSType() + "} */\n";
} else {
return "";
}
}
/**
* @param fnNode A node for a function for which to generate a type annotation
*/
private String getFunctionAnnotation(Node fnNode) {
Preconditions.checkState(fnNode.getType() == Token.FUNCTION);
StringBuilder sb = new StringBuilder("/**\n");
JSType type = fnNode.getJSType();
if (type == null || type.isUnknownType()) {
return "";
}
FunctionType funType = (FunctionType) fnNode.getJSType();
// We need to use the child nodes of the function as the nodes for the
// parameters of the function type do not have the real parameter names.
// FUNCTION
// NAME
// LP
// NAME param1
// NAME param2
if (fnNode != null) {
Node paramNode = NodeUtil.getFnParameters(fnNode).getFirstChild();
// Param types
for (Node n : funType.getParameters()) {
// Bail out if the paramNode is not there.
if (paramNode == null) {
break;
}
sb.append(" * @param {" + n.getJSType() + "} ");
sb.append(paramNode.getString());
sb.append("\n");
paramNode = paramNode.getNext();
}
}
// Return type
JSType retType = funType.getReturnType();
if (retType != null && !retType.isUnknownType() && !retType.isEmptyType()) {
sb.append(" * @return {" + retType + "}\n");
}
// Constructor/interface
if (funType.isConstructor() || funType.isInterface()) {
FunctionType superConstructor = funType.getSuperClassConstructor();
if (super
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> } else if (NodeUtil.isFunctionDeclaration(n) ||
parent.getType() == Token.NAME) {
return normalizeClassType(n.getJSType());
}
return null;
}
/**
* Normalize the type of a constructor, its instance, and its prototype
* all down to the same type (the instance type).
*/
private JSType normalizeClassType(JSType type) {
if (type == null || type.isUnknownType()) {
return type;
} else if (type.isConstructor()) {
return ((FunctionType) type).getInstanceType();
} else if (type.isFunctionPrototypeType()) {
FunctionType owner = ((FunctionPrototypeType) type).getOwnerFunction();
if (owner.isConstructor()) {
return owner.getInstanceType();
}
}
return type;
}
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
checkNameDeprecation(t, n, parent);
checkNameVisibility(t, n, parent);
break;
case Token.GETPROP:
checkPropertyDeprecation(t, n, parent);
checkPropertyVisibility(t, n, parent);
break;
case Token.NEW:
checkConstructorDeprecation(t, n, parent);
break;
}
}
/**
* Checks the given NEW node to ensure that access restrictions are obeyed.
*/
private void checkConstructorDeprecation(NodeTraversal t, Node n,
Node parent) {
JSType type = n.getJSType();
if (type != null) {
String deprecationInfo = getTypeDeprecationInfo(type);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
t.makeError(n, DEPRECATED_CLASS_REASON,
type.toString(), deprecationInfo));
} else {
compiler.report(
t.makeError(n, DEPRECATED_CLASS, type.toString()));
}
}
}
}
/**
* Checks the given NAME node to ensure that access restrictions are obeyed.
*/
private void checkNameDe
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> example,
* new PrivateCtor_(); // not legal
* PrivateCtor_.newInstance(); // legal
* x instanceof PrivateCtor_ // legal
*
* This is a weird special case, because our visibility system is inherited
* from Java, and JavaScript has no distinction between classes and
* constructors like Java does.
*
* We may want to revisit this if we decide to make the restrictions tighter.
*/
private static boolean isValidPrivateConstructorAccess(Node parent) {
return parent.getType() != Token.NEW;
}
/**
* Determines whether a deprecation warning should be emitted.
* @param t The current traversal.
* @param n The node which we are checking.
* @param parent The parent of the node which we are checking.
*/
private boolean shouldEmitDeprecationWarning(
NodeTraversal t, Node n, Node parent) {
// In the global scope, there are only two kinds of accesses that should
// be flagged for warnings:
// 1) Calls of deprecated functions and methods.
// 2) Instantiations of deprecated classes.
// For now, we just let everything else by.
if (t.inGlobalScope()) {
if (!((parent.getType() == Token.CALL && parent.getFirstChild() == n) ||
n.getType() == Token.NEW)) {
return false;
}
}
// We can always assign to a deprecated property, to keep it up to date.
if (n.getType() == Token.GETPROP && n == parent.getFirstChild() &&
NodeUtil.isAssignmentOp(parent)) {
return false;
}
return !canAccessDeprecatedTypes(t);
}
/**
* Returns whether it's currently ok to access deprecated names and
* properties.
*
* There are 3 exceptions when we're allowed to use a deprecated
* type or property:
* 1) When we're in a deprecated function.
* 2) When we're in a deprecated class.
* 3) When we're in a static method of a deprecated class.
*/
private boolean canAccessDeprecatedTypes(NodeTraversal t) {
Node scopeRoot = t.getScopeRoot();
Node scopeRootParent = scopeRoot.getParent();
return
// Case #1
(deprecatedDepth > 0) ||
// Case #
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> FunctionParamBuilder(JSTypeRegistry registry) {
this.registry = registry;
}
/**
* Add parameters of the given type to the end of the param list.
* @return False if this is called after optional params are added.
*/
public boolean addRequiredParams(JSType ...types) {
if (hasOptionalOrVarArgs()) {
return false;
}
for (JSType type : types) {
newParameter(type);
}
return true;
}
/**
* Add optional parameters of the given type to the end of the param list.
* @param types Types for each optional parameter. The builder will make them
* undefineable.
* @return False if this is called after var args are added.
*/
public boolean addOptionalParams(JSType ...types) {
if (hasVarArgs()) {
return false;
}
for (JSType type : types) {
newParameter(registry.createOptionalType(type)).setOptionalArg(true);
}
return true;
}
/**
* Add variable arguments to the end of the parameter list.
* @return False if this is called after var args are added.
*/
public boolean addVarArgs(JSType type) {
if (hasVarArgs()) {
return false;
}
// There are two types of variable argument functions:
// 1) Programmer-defined var args
// 2) Native bottom types that can accept any argument.
// For the first one, "undefined" is a valid value for all arguments.
// For the second, we do not want to cast it up to undefined.
if (!type.isEmptyType()) {
type = registry.createOptionalType(type);
}
newParameter(type).setVarArgs(true);
return true;
}
/**
* Copies the parameter specification from the given node.
*/
public void newParameterFromNode(Node n) {
Node newParam = newParameter(n.getJSType());
newParam.setVarArgs(n.isVarArgs());
newParam.setOptionalArg(n.isOptionalArg());
}
// Add a parameter to the list with the given type.
private Node newParameter(JSType type) {
Node paramNode = Node.newString(Token.NAME, "");
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> this to generate deps files. (We're only using it for
// symbol dependencies.)
DependencyInfo info = (new JsFileParser(errorManager)).parseFile(
getName(), getName(), getCode());
provides.addAll(info.getProvides());
requires.addAll(info.getRequires());
generatedDependencyInfoFromSource = true;
}
}
}
private static class DepsFinder {
private final List<String> provides = Lists.newArrayList();
private final List<String> requires = Lists.newArrayList();
private final CodingConvention codingConvention =
new ClosureCodingConvention();
void visitTree(Node n) {
visitSubtree(n, null);
}
void visitSubtree(Node n, Node parent) {
if (n.getType() == Token.CALL) {
String require =
codingConvention.extractClassNameIfRequire(n, parent);
if (require != null) {
requires.add(require);
}
String provide =
codingConvention.extractClassNameIfProvide(n, parent);
if (provide != null) {
provides.add(provide);
}
return;
} else if (parent != null &&
parent.getType() != Token.EXPR_RESULT &&
parent.getType() != Token.SCRIPT) {
return;
}
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
visitSubtree(child, n);
}
}
}
/**
* Gets the source line for the indicated line number.
*
* @param lineNumber the line number, 1 being the first line of the file.
* @return The line indicated. Does not include the newline at the end
* of the file. Returns {@code null} if it does not exist,
* or if there was an IO exception.
*/
public String getLine(int lineNumber) {
return getSourceFile().getLine(lineNumber);
}
/**
* Get a region around the indicated line number. The exact definition of a
* region is implementation specific, but it must contain the line indicated
* by the line number. A region must not start or end by a carriage return.
*
* @param lineNumber the line number, 1 being the first line of the file.
* @return The line indicated. Returns {@code null} if
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> abstract T processSwitchCase(SwitchCase caseNode);
abstract T processSwitchStatement(SwitchStatement statementNode);
abstract T processThrowStatement(ThrowStatement statementNode);
abstract T processTryStatement(TryStatement statementNode);
abstract T processUnaryExpression(UnaryExpression exprNode);
abstract T processVariableDeclaration(VariableDeclaration declarationNode);
abstract T processVariableInitializer(VariableInitializer initializerNode);
abstract T processWhileLoop(WhileLoop loopNode);
abstract T processWithStatement(WithStatement statementNode);
abstract T processIllegalToken(AstNode node);
public T process(AstNode node) {
switch (node.getType()) {
case Token.ADD:
case Token.AND:
case Token.BITAND:
case Token.BITOR:
case Token.BITXOR:
case Token.COMMA:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GT:
case Token.IN:
case Token.INSTANCEOF:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.OR:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.URSH:
return processInfixExpression((InfixExpression) node);
case Token.ARRAYLIT:
return processArrayLiteral((ArrayLiteral) node);
case Token.ASSIGN:
case Token.ASSIGN_ADD:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_DIV:
case Token.ASSIGN_LSH:
case Token.ASSIGN_MOD:
case Token.ASSIGN_MUL:
case Token.ASSIGN_RSH:
case Token.ASSIGN_SUB:
case Token.ASSIGN_URSH:
return processAssignment((Assignment) node);
case Token.BITNOT:
case Token.DEC:
case Token.DELPROP:
case Token.INC:
case Token.NEG:
case Token.NOT:
case Token.POS:
case Token.TYPEOF:
case Token.VOID:
return processUnaryExpression((UnaryExpression) node);
case Token.BLOCK:
if (node instanceof Block
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>((PropertyGet) node);
case Token.HOOK:
return processConditionalExpression((ConditionalExpression) node);
case Token.IF:
return processIfStatement((IfStatement) node);
case Token.LABEL:
return processLabel((Label) node);
case Token.LP:
return processParenthesizedExpression((ParenthesizedExpression) node);
case Token.NAME:
return processName((Name) node);
case Token.NEW:
return processNewExpression((NewExpression) node);
case Token.NUMBER:
return processNumberLiteral((NumberLiteral) node);
case Token.OBJECTLIT:
return processObjectLiteral((ObjectLiteral) node);
case Token.REGEXP:
return processRegExpLiteral((RegExpLiteral) node);
case Token.RETURN:
return processReturnStatement((ReturnStatement) node);
case Token.SCRIPT:
return processAstRoot((AstRoot) node);
case Token.STRING:
return processStringLiteral((StringLiteral) node);
case Token.SWITCH:
return processSwitchStatement((SwitchStatement) node);
case Token.THROW:
return processThrowStatement((ThrowStatement) node);
case Token.TRY:
return processTryStatement((TryStatement) node);
case Token.VAR:
if (node instanceof VariableDeclaration) {
return processVariableDeclaration((VariableDeclaration) node);
} else if (node instanceof VariableInitializer) {
return processVariableInitializer((VariableInitializer) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.WHILE:
return processWhileLoop((WhileLoop) node);
case Token.WITH:
return processWithStatement((WithStatement) node);
}
return processIllegalToken(node);
}
}
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>/*
* Copyright 2004 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.TokenStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
/**
* NodeUtil contains utilities that get properties from the Node object.
*
*
*
*/
public final class NodeUtil {
// TODO(user): Eliminate this class and make all of the static methods
// instance methods of com.google.javascript.rhino.Node.
/** the set of builtin constructors that don't have side effects. */
private static final Set<String> CONSTRUCTORS_WITHOUT_SIDE_EFFECTS =
new HashSet<String>(Arrays.asList(
"Array",
"Date",
"Error",
"Object",
"RegExp",
"XMLHttpRequest"));
// Utility class; do not instantiate.
private NodeUtil() {}
/**
* Gets the boolean value of a node that represents a literal. This method
* effectively
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> emulates the <code>Boolean()</code> JavaScript cast function.
*
* @throws IllegalArgumentException If {@code n} is not a literal value
*/
static boolean getBooleanValue(Node n) {
switch (n.getType()) {
case Token.STRING:
return n.getString().length() > 0;
case Token.NUMBER:
return n.getDouble() != 0;
case Token.NULL:
case Token.FALSE:
case Token.VOID:
return false;
case Token.NAME:
String name = n.getString();
if ("undefined".equals(name)
|| "NaN".equals(name)) {
// We assume here that programs don't change the value of the keyword
// undefined to something other than the value undefined.
return false;
} else if ("Infinity".equals(name)) {
return true;
}
break;
case Token.TRUE:
case Token.ARRAYLIT:
case Token.OBJECTLIT:
case Token.REGEXP:
return true;
}
throw new IllegalArgumentException("Non-literal value: " + n);
}
/**
* Gets the value of a node as a String, or null if it cannot be converted.
* When it returns a non-null String, this method effectively emulates the
* <code>String()</code> JavaScript cast function.
*/
static String getStringValue(Node n) {
// TODO(user): Convert constant array, object, and regex literals as well.
switch (n.getType()) {
case Token.NAME:
case Token.STRING:
return n.getString();
case Token.NUMBER:
double value = n.getDouble();
long longValue = (long) value;
// Return "1" instead of "1.0"
if (longValue == value) {
return Long.toString(longValue);
} else {
return Double.toString(n.getDouble());
}
case Token.FALSE:
case Token.TRUE:
case Token.NULL:
return Node.tokenToName(n.getType());
case Token.VOID:
return "undefined";
}
return null;
}
/**
* Gets the function's name. This method recognizes five forms:
* <ul>
* <li>{@code function name() ...}</li>
* <li>{@code var
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> name = function() ...}</li>
* <li>{@code qualified.name = function() ...}</li>
* <li>{@code var name2 = function name1() ...}</li>
* <li>{@code qualified.name2 = function name1() ...}</li>
* </ul>
* In two last cases with named function expressions, the second name is
* returned (the variable of qualified name).
*
* @param n a node whose type is {@link Token#FUNCTION}
* @return the function's name, or {@code null} if it has no name
*/
static String getFunctionName(Node n) {
Node parent = n.getParent();
String name = n.getFirstChild().getString();
switch (parent.getType()) {
case Token.NAME:
// var name = function() ...
// var name2 = function name1() ...
return parent.getString();
case Token.ASSIGN:
// qualified.name = function() ...
// qualified.name2 = function name1() ...
return parent.getFirstChild().getQualifiedName();
default:
// function name() ...
return name != null && name.length() != 0 ? name : null;
}
}
/**
* Returns true if this is an immutable value.
*/
static boolean isImmutableValue(Node n) {
switch (n.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.NULL:
case Token.TRUE:
case Token.FALSE:
case Token.VOID:
return true;
case Token.NEG:
return isImmutableValue(n.getFirstChild());
case Token.NAME:
String name = n.getString();
// We assume here that programs don't change the value of the keyword
// undefined to something other than the value undefined.
return "undefined".equals(name)
|| "Infinity".equals(name)
|| "NaN".equals(name);
}
return false;
}
/**
* Returns true if this is a literal value. We define a literal value
* as any node that evaluates to the same thing regardless of when or
* where it is evaluated. So /xyz/ and [3, 5] are literals, but
* function() { return a; } is not.
*/
static boolean isLiteralValue(Node
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> n) {
// TODO(nicksantos): Refine this function to catch more literals.
switch (n.getType()) {
case Token.ARRAYLIT:
case Token.OBJECTLIT:
case Token.REGEXP:
// Return true only if all children are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!isLiteralValue(child)) {
return false;
}
}
return true;
default:
return isImmutableValue(n);
}
}
/**
* Determines whether the given value may be assigned to a define.
*
* @param val The value being assigned.
* @param defines The list of names of existing defines.
*/
static boolean isValidDefineValue(Node val, Set<String> defines) {
switch (val.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.TRUE:
case Token.FALSE:
return true;
// Single operators are valid if the child is valid.
case Token.BITAND:
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.NOT:
case Token.NEG:
return isValidDefineValue(val.getFirstChild(), defines);
// Names are valid if and only if they are defines themselves.
case Token.NAME:
case Token.GETPROP:
if (val.isQualifiedName()) {
return defines.contains(val.getQualifiedName());
}
}
return false;
}
/**
* Returns whether this a BLOCK node with no children.
*
* @param block The node.
*/
static boolean isEmptyBlock(Node block) {
if (block.getType() != Token.BLOCK) {
return false;
}
for (Node n = block.getFirstChild(); n != null; n = n.getNext()) {
if (n.getType() != Token.EMPTY) {
return false;
}
}
return true;
}
/**
* A "simple" operator is one whose children are expressions,
* has no direct side-effects (unlike '+='), and has no
* conditional aspects (unlike '||').
*/
static boolean isSimpleOperatorType(int type) {
switch (type) {
case Token.
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> change state. Otherwise, we assume
* that they have no side effects.
*/
private static boolean checkForStateChangeHelper(
Node n, boolean checkForNewObjects, AbstractCompiler compiler) {
// Rather than id which ops may have side effects, id the ones
// that we know to be safe
switch (n.getType()) {
// other side-effect free statements and expressions
case Token.AND:
case Token.BLOCK:
case Token.EXPR_RESULT:
case Token.HOOK:
case Token.IF:
case Token.IN:
case Token.LP:
case Token.NUMBER:
case Token.OR:
case Token.THIS:
case Token.TRUE:
case Token.FALSE:
case Token.NULL:
case Token.STRING:
case Token.SWITCH:
case Token.TRY:
case Token.EMPTY:
break;
// Throws are by definition side effects
case Token.THROW:
return true;
case Token.OBJECTLIT:
case Token.ARRAYLIT:
case Token.REGEXP:
if (checkForNewObjects) {
return true;
}
break;
case Token.VAR: // empty var statement (no declaration)
case Token.NAME: // variable by itself
if (n.getFirstChild() != null)
return true;
break;
case Token.FUNCTION:
// Function expressions don't have side-effects, but function
// declarations change the namespace. Either way, we don't need to
// check the children, since they aren't executed at declaration time.
return !isFunctionExpression(n);
case Token.NEW:
if (checkForNewObjects) {
return true;
}
if (!constructorCallHasSideEffects(n)) {
// loop below will see if the constructor parameters have
// side-effects
break;
}
return true;
case Token.CALL:
// calls to functions that have no side effects have the no
// side effect property set.
if (!functionCallHasSideEffects(n, compiler)) {
// loop below will see if the function parameters have
// side-effects
break;
}
return true;
default:
if (isSimpleOperatorType(n.getType()))
break;
if (isAssignmentOp(n)) {
// Assignments will have side effects if
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>
// a) The RHS has side effects, or
// b) The LHS has side effects, or
// c) A name on the LHS will exist beyond the life of this statement.
if (checkForStateChangeHelper(
n.getFirstChild(), checkForNewObjects, compiler) ||
checkForStateChangeHelper(
n.getLastChild(), checkForNewObjects, compiler)) {
return true;
}
Node current = n.getFirstChild();
for (;
current.getType() == Token.GETPROP ||
current.getType() == Token.GETELEM;
current = current.getFirstChild()) { }
return !(isLiteralValue(current) ||
current.getType() == Token.FUNCTION);
}
return true;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (checkForStateChangeHelper(c, checkForNewObjects, compiler)) {
return true;
}
}
return false;
}
/**
* Do calls to this constructor have side effects?
*
* @param callNode - construtor call node
*/
static boolean constructorCallHasSideEffects(Node callNode) {
Preconditions.checkArgument(
callNode.getType() == Token.NEW,
"Expected NEW node, got " + Token.name(callNode.getType()));
if (callNode.isNoSideEffectsCall()) {
return false;
}
Node nameNode = callNode.getFirstChild();
if (nameNode.getType() == Token.NAME &&
CONSTRUCTORS_WITHOUT_SIDE_EFFECTS.contains(nameNode.getString())) {
return false;
}
return true;
}
private static final Set<String> REGEXP_METHODS =
ImmutableSet.of("test", "exec");
private static final Set<String> STRING_REGEXP_METHODS =
ImmutableSet.of("match", "replace", "search", "split");
/**
* Returns true if calls to this function have side effects.
*
* @param callNode - function call node
*/
static boolean functionCallHasSideEffects(
Node callNode) {
return functionCallHasSideEffects(callNode, null);
}
/**
* Returns true if calls to this function have side effects.
*
* @param callNode The
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> call node to inspected.
* @param compiler A compiler object to provide program state changing
* context information. Can be null.
*/
static boolean functionCallHasSideEffects(
Node callNode, @Nullable AbstractCompiler compiler) {
Preconditions.checkArgument(
callNode.getType() == Token.CALL,
"Expected CALL node, got " + Token.name(callNode.getType()));
if (callNode.isNoSideEffectsCall()) {
return false;
}
Node nameNode = callNode.getFirstChild();
// Built-in functions with no side effects.
if (nameNode.getType() == Token.NAME) {
String name = nameNode.getString();
if (name.equals("String")) {
return false;
}
} else if (nameNode.getType() == Token.GETPROP) {
// Functions in the "Math" namespace have no side effects.
if (nameNode.getFirstChild().getType() == Token.NAME) {
String namespaceName = nameNode.getFirstChild().getString();
if (namespaceName.equals("Math")) {
return false;
}
}
if (compiler != null && !compiler.hasRegExpGlobalReferences()) {
if (nameNode.getFirstChild().getType() == Token.REGEXP
&& REGEXP_METHODS.contains(nameNode.getLastChild().getString())) {
return false;
} else if (nameNode.getFirstChild().getType() == Token.STRING
&& STRING_REGEXP_METHODS.contains(
nameNode.getLastChild().getString())) {
Node param = nameNode.getNext();
if (param != null &&
(param.getType() == Token.STRING
|| param.getType() == Token.REGEXP))
return false;
}
}
}
return true;
}
/**
* Returns true if the current node's type implies side effects.
*
* This is a non-recursive version of the may have side effects
* check; used to check wherever the current node's type is one of
* the reason's why a subtree has side effects.
*/
static boolean nodeTypeMayHaveSideEffects(Node n) {
if (isAssignmentOp(n)) {
return true;
}
switch(n.getType()) {
case Token.CALL:
case Token.DELPROP
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>:
case Token.NEW:
case Token.DEC:
case Token.INC:
case Token.THROW:
return true;
case Token.NAME:
// A variable definition.
return n.hasChildren();
default:
return false;
}
}
/**
* @return Whether the tree can be affected by side-effects or
* has side-effects.
*/
static boolean canBeSideEffected(Node n) {
Set<String> emptySet = Collections.emptySet();
return canBeSideEffected(n, emptySet);
}
/**
* @param knownConstants A set of names known to be constant value at
* node 'n' (such as locals that are last written before n can execute).
* @return Whether the tree can be affected by side-effects or
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
case Token.FUNCTION:
// Function expression are not changed by side-effects,
// and function declarations are not part of expressions.
Preconditions.checkState(isFunctionExpression(n));
return false;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
* 3 logical-or ||
* 4 logical-and &&
* 5 bitwise-or |
* 6 bitwise-xor ^
* 7 bitwise-
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>:
case Token.FALSE:
case Token.FUNCTION:
case Token.GETELEM:
case Token.GETPROP:
case Token.GET_REF:
case Token.IF:
case Token.LP:
case Token.NAME:
case Token.NULL:
case Token.NUMBER:
case Token.OBJECTLIT:
case Token.REGEXP:
case Token.RETURN:
case Token.STRING:
case Token.THIS:
case Token.TRUE:
return 15;
default: throw new Error("Unknown precedence for " +
Node.tokenToName(type) +
" (type " + type + ")");
}
}
/**
* Returns true if the operator is associative.
* e.g. (a * b) * c = a * (b * c)
* Note: "+" is not associative because it is also the concatentation
* for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2
*/
static boolean isAssociative(int type) {
switch (type) {
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITAND:
return true;
default:
return false;
}
}
static boolean isAssignmentOp(Node n) {
switch (n.getType()){
case Token.ASSIGN:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
return true;
}
return false;
}
static int getOpFromAssignmentOp(Node n) {
switch (n.getType()){
case Token.ASSIGN_BITOR:
return Token.BITOR;
case Token.ASSIGN_BITXOR:
return Token.BITXOR;
case Token.ASSIGN_BITAND:
return Token.BITAND;
case Token.ASSIGN_LSH:
return Token.LSH;
case Token
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>.ASSIGN_RSH:
return Token.RSH;
case Token.ASSIGN_URSH:
return Token.URSH;
case Token.ASSIGN_ADD:
return Token.ADD;
case Token.ASSIGN_SUB:
return Token.SUB;
case Token.ASSIGN_MUL:
return Token.MUL;
case Token.ASSIGN_DIV:
return Token.DIV;
case Token.ASSIGN_MOD:
return Token.MOD;
}
throw new IllegalArgumentException("Not an assiment op");
}
static boolean isExpressionNode(Node n) {
return n.getType() == Token.EXPR_RESULT;
}
/**
* Determines if the given node contains a function declaration.
*/
static boolean containsFunctionDeclaration(Node n) {
return containsType(n, Token.FUNCTION);
}
/**
* Returns true if the subtree contains references to 'this' keyword
*/
static boolean referencesThis(Node n) {
return containsType(n, Token.THIS);
}
/**
* Is this a GETPROP or GETELEM node?
*/
static boolean isGet(Node n) {
return n.getType() == Token.GETPROP
|| n.getType() == Token.GETELEM;
}
/**
* Is this a GETPROP node?
*/
static boolean isGetProp(Node n) {
return n.getType() == Token.GETPROP;
}
/**
* Is this a NAME node?
*/
static boolean isName(Node n) {
return n.getType() == Token.NAME;
}
/**
* Is this a NEW node?
*/
static boolean isNew(Node n) {
return n.getType() == Token.NEW;
}
/**
* Is this a VAR node?
*/
static boolean isVar(Node n) {
return n.getType() == Token.VAR;
}
/**
* Is this node the name of a variable being declared?
*
* @param n The node
* @return True if {@code n} is NAME and {@code parent} is VAR
*/
static boolean isVarDeclaration(Node n) {
// There is no need to verify that parent != null because a NAME node
// always has a parent in a valid parse tree.
return n.getType() == Token.NAME
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> n) {
switch (n.getType()) {
case Token.IF:
case Token.WHILE:
return n.getFirstChild();
case Token.DO:
return n.getLastChild();
case Token.FOR:
switch (n.getChildCount()) {
case 3:
return null;
case 4:
return n.getFirstChild().getNext();
}
throw new IllegalArgumentException("malformed 'for' statement " + n);
case Token.CASE:
return null;
}
throw new IllegalArgumentException(n + " does not have a condition.");
}
/**
* @return Whether the node is of a type that contain other statements.
*/
static boolean isStatementBlock(Node n) {
return n.getType() == Token.SCRIPT || n.getType() == Token.BLOCK;
}
/**
* @return Whether the node is used as a statement.
*/
static boolean isStatement(Node n) {
Node parent = n.getParent();
// It is not possible to determine definitely if a node is a statement
// or not if it is not part of the AST. A FUNCTION node can be
// either part of an expression or a statement.
Preconditions.checkState(parent != null);
switch (parent.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.LABEL:
return true;
default:
return false;
}
}
/** Whether the node is part of a switch statement. */
static boolean isSwitchCase(Node n) {
return n.getType() == Token.CASE || n.getType() == Token.DEFAULT;
}
/**
* @return Whether the name is a reference to a variable, function or
* function parameter (not a label or a empty function expression name).
*/
static boolean isReferenceName(Node n) {
return isName(n) && !n.getString().isEmpty();
}
/** @return Whether the node is a label name. */
static boolean isLabelName(Node n) {
return (n != null && n.getType() == Token.LABEL_NAME);
}
/** Whether the child node is the FINALLY block of a try. */
static boolean isTryFinallyNode(Node parent, Node child) {
return parent.getType() == Token.TRY && parent.
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>getChildCount() == 3
&& child == parent.getLastChild();
}
/** Safely remove children while maintaining a valid node structure. */
static void removeChild(Node parent, Node node) {
// Node parent = node.getParent();
if (isStatementBlock(parent)
|| isSwitchCase(node)
|| isTryFinallyNode(parent, node)) {
// A statement in a block can simply be removed.
parent.removeChild(node);
} else if (parent.getType() == Token.VAR) {
if (parent.hasMoreThanOneChild()) {
parent.removeChild(node);
} else {
// Remove the node from the parent, so it can be reused.
parent.removeChild(node);
// This would leave an empty VAR, remove the VAR itself.
removeChild(parent.getParent(), parent);
}
} else if (node.getType() == Token.BLOCK) {
// Simply empty the block. This maintains source location and
// "synthetic"-ness.
node.detachChildren();
} else if (parent.getType() == Token.LABEL
&& node == parent.getLastChild()) {
// Remove the node from the parent, so it can be reused.
parent.removeChild(node);
// A LABEL without children can not be referred to, remove it.
removeChild(parent.getParent(), parent);
} else if (parent.getType() == Token.FOR
&& parent.getChildCount() == 4) {
// Only Token.FOR can have an Token.EMPTY other control structure
// need something for the condition. Others need to be replaced
// or the structure removed.
parent.replaceChild(node, new Node(Token.EMPTY));
} else {
throw new IllegalStateException("Invalid attempt to remove node: " +
node.toString() + " of "+ parent.toString());
}
}
/**
* Merge a block with its parent block.
* @return Whether the block was removed.
*/
static boolean tryMergeBlock(Node block) {
Preconditions.checkState(block.getType() == Token.BLOCK);
Node parent = block.getParent();
// Try to remove the block if its parent is a block/script or if its
// parent is label and it has exactly one child.
if (isStatementBlock(parent)) {
Node
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> previous = block;
while (block.hasChildren()) {
Node child = block.removeFirstChild();
parent.addChildAfter(child, previous);
previous = child;
}
parent.removeChild(block);
return true;
} else if (parent.getType() == Token.LABEL && block.hasOneChild()) {
parent.replaceChild(block, block.removeFirstChild());
return true;
} else {
return false;
}
}
/**
* Is this a CALL node?
*/
static boolean isCall(Node n) {
return n.getType() == Token.CALL;
}
/**
* Is this a FUNCTION node?
*/
static boolean isFunction(Node n) {
return n.getType() == Token.FUNCTION;
}
/**
* Return a BLOCK node for the given FUNCTION node.
*/
static Node getFunctionBody(Node fn) {
Preconditions.checkArgument(isFunction(fn));
return fn.getLastChild();
}
/**
* Is this a THIS node?
*/
static boolean isThis(Node node) {
return node.getType() == Token.THIS;
}
/**
* Is this node or any of its children a CALL?
*/
static boolean containsCall(Node n) {
return containsType(n, Token.CALL);
}
/**
* Is this node a function declaration? A function declaration is a function
* that has a name that is added to the current scope (i.e. a function that
* is not part of a expression; see {@link #isFunctionExpression}).
*/
static boolean isFunctionDeclaration(Node n) {
return n.getType() == Token.FUNCTION && isStatement(n);
}
/**
* Is this node a hoisted function declaration? A function declaration in the
* scope root is hoisted to the top of the scope.
* See {@link #isFunctionDeclaration}).
*/
static boolean isHoistedFunctionDeclaration(Node n) {
return isFunctionDeclaration(n)
&& (n.getParent().getType() == Token.SCRIPT
|| n.getParent().getParent().getType() == Token.FUNCTION);
}
/**
* Is a FUNCTION node an function expression? An function expression is one
* that has either no name or a name that is not added to the current scope.
*
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>
* <p>Some examples of function expressions:
* <pre>
* (function () {})
* (function f() {})()
* [ function f() {} ]
* var f = function f() {};
* for (function f() {};;) {}
* </pre>
*
* <p>Some examples of functions that are <em>not</em> expressions:
* <pre>
* function f() {}
* if (x); else function f() {}
* for (;;) { function f() {} }
* </pre>
*
* @param n A node
* @return Whether n is an function used within an expression.
*/
static boolean isFunctionExpression(Node n) {
return n.getType() == Token.FUNCTION && !isStatement(n);
}
/**
* Determines if a function takes a variable number of arguments by
* looking for references to the "arguments" var_args object.
*/
static boolean isVarArgsFunction(Node function) {
Preconditions.checkArgument(isFunction(function));
return isNameReferenced(
function.getLastChild(),
"arguments",
Predicates.<Node>not(new MatchNodeType(Token.FUNCTION)));
}
/**
* @return Whether node is a call to methodName.
* a.f(...)
* a['f'](...)
*/
static boolean isObjectCallMethod(Node callNode, String methodName) {
if (callNode.getType() == Token.CALL) {
Node functionIndentifyingExpression = callNode.getFirstChild();
if (isGet(functionIndentifyingExpression)) {
Node last = functionIndentifyingExpression.getLastChild();
if (last != null && last.getType() == Token.STRING) {
String propName = last.getString();
return (propName.equals(methodName));
}
}
}
return false;
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.call(...)
* x['call'](...)
*/
static boolean isFunctionObjectCall(Node callNode) {
return isObjectCallMethod(callNode, "call");
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.apply(...)
* x['apply'](...)
*/
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> static boolean isFunctionObjectApply(Node callNode) {
return isObjectCallMethod(callNode, "apply");
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.call(...)
* x['call'](...)
* where x is a NAME node.
*/
static boolean isSimpleFunctionObjectCall(Node callNode) {
if (isFunctionObjectCall(callNode)) {
if (callNode.getFirstChild().getFirstChild().getType() == Token.NAME) {
return true;
}
}
return false;
}
/**
* Determines whether this node is strictly on the left hand side of an assign
* or var initialization. Notably, this does not include all L-values, only
* statements where the node is used only as an L-value.
*
* @param n The node
* @param parent Parent of the node
* @return True if n is the left hand of an assign
*/
static boolean isLhs(Node n, Node parent) {
return (parent.getType() == Token.ASSIGN && parent.getFirstChild() == n) ||
parent.getType() == Token.VAR;
}
/**
* Determines whether a node represents an object literal key
* (e.g. key1 in {key1: value1, key2: value2}).
*
* @param node A node
* @param parent The node's parent
*/
static boolean isObjectLitKey(Node node, Node parent) {
if (node.getType() == Token.STRING && parent.getType() == Token.OBJECTLIT) {
int index = 0;
for (Node current = parent.getFirstChild();
current != null;
current = current.getNext()) {
if (current == node) {
return index % 2 == 0;
}
index++;
}
}
return false;
}
/**
* Converts an operator's token value (see {@link Token}) to a string
* representation.
*
* @param operator the operator's token value to convert
* @return the string representation or {@code null} if the token value is
* not an operator
*/
static String opToStr(int operator) {
switch (operator) {
case Token.BITOR: return "|";
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> operator) {
String res = opToStr(operator);
if (res == null) {
throw new Error("Unknown op " + operator + ": " +
Token.name(operator));
}
return res;
}
/**
* @return true if n or any of its children are of the specified type.
* Does not traverse into functions.
*/
static boolean containsTypeInOuterScope(Node node, int type) {
return containsType(node, type,
Predicates.<Node>not(new MatchNodeType(Token.FUNCTION)));
}
/**
* @return true if n or any of its children are of the specified type
*/
static boolean containsType(Node node,
int type,
Predicate<Node> traverseChildrenPred) {
return has(node, new MatchNodeType(type), traverseChildrenPred);
}
/**
* @return true if n or any of its children are of the specified type
*/
static boolean containsType(Node node, int type) {
return containsType(node, type, Predicates.<Node>alwaysTrue());
}
/**
* Given a node tree, finds all the VAR declarations in that tree that are
* not in an inner scope. Then adds a new VAR node at the top of the current
* scope that redeclares them, if necessary.
*/
static void redeclareVarsInsideBranch(Node branch) {
Collection<Node> vars = getVarsDeclaredInBranch(branch);
if (vars.isEmpty()) {
return;
}
Node parent = getAddingRoot(branch);
for (Node nameNode : vars) {
Node var = new Node(
Token.VAR,
Node.newString(Token.NAME, nameNode.getString())
.copyInformationFrom(nameNode))
.copyInformationFrom(nameNode);
copyNameAnnotations(nameNode, var.getFirstChild());
parent.addChildToFront(var);
}
}
/**
* Copy any annotations that follow a named value.
* @param source
* @param destination
*/
static void copyNameAnnotations(Node source, Node destination) {
if (source.getBooleanProp(Node.IS_CONSTANT_NAME)) {
destination.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
}
/**
* Gets a Node at
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> the top of the current scope where we can add new var
* declarations as children.
*/
private static Node getAddingRoot(Node n) {
Node addingRoot = null;
Node ancestor = n;
while (null != (ancestor = ancestor.getParent())) {
int type = ancestor.getType();
if (type == Token.SCRIPT) {
addingRoot = ancestor;
break;
} else if (type == Token.FUNCTION) {
addingRoot = ancestor.getLastChild();
break;
}
}
// make sure that the adding root looks ok
Preconditions.checkState(addingRoot.getType() == Token.BLOCK ||
addingRoot.getType() == Token.SCRIPT);
Preconditions.checkState(addingRoot.getFirstChild() == null ||
addingRoot.getFirstChild().getType() != Token.SCRIPT);
return addingRoot;
}
/** Creates function name(params_0, ..., params_n) { body }. */
public static Node newFunctionNode(String name, List<Node> params,
Node body, int lineno, int charno) {
Node parameterParen = new Node(Token.LP, lineno, charno);
for (Node param : params) {
parameterParen.addChildToBack(param);
}
Node function = new Node(Token.FUNCTION, lineno, charno);
function.addChildrenToBack(
Node.newString(Token.NAME, name, lineno, charno));
function.addChildToBack(parameterParen);
function.addChildToBack(body);
return function;
}
/**
* Creates a node representing a qualified name.
*
* @param name A qualified name (e.g. "foo" or "foo.bar.baz")
* @param lineno The source line offset.
* @param charno The source character offset from start of the line.
* @return A NAME or GETPROP node
*/
public static Node newQualifiedNameNode(String name, int lineno, int charno) {
int endPos = name.indexOf('.');
if (endPos == -1) {
return Node.newString(Token.NAME, name, lineno, charno);
}
Node node = Node.newString(Token.NAME, name.substring(0, endPos),
lineno, charno);
int startPos;
do {
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>Token.NAME, name);
nameNode.copyInformationFrom(basisNode);
return nameNode;
}
/**
* Creates a new node representing an *existing* name, copying over the source
* location information from the basis node and assigning the given original
* name to the node.
*
* @param name The name for the new NAME node.
* @param basisNode The node that represents the name as currently found in
* the AST.
* @param originalName The original name of the item being represented by the
* NAME node. Used for debugging information.
*
* @return The node created.
*/
static Node newName(String name, Node basisNode, String originalName) {
Node nameNode = newName(name, basisNode);
nameNode.putProp(Node.ORIGINALNAME_PROP, originalName);
return nameNode;
}
/** Test if all characters in the string are in the Basic Latin (aka ASCII)
* character set - that they have UTF-16 values equal to or below 0x7f.
* This check can find which identifiers with Unicode characters need to be
* escaped in order to allow resulting files to be processed by non-Unicode
* aware UNIX tools and editors.
* *
* See http://en.wikipedia.org/wiki/Latin_characters_in_Unicode
* for more on Basic Latin.
*
* @param s The string to be checked for ASCII-goodness.
*
* @return True if all characters in the string are in Basic Latin set.
*/
static boolean isLatin(String s) {
char LARGEST_BASIC_LATIN = 0x7f;
int len = s.length();
for (int index = 0; index < len; index++) {
char c = s.charAt(index);
if (c > LARGEST_BASIC_LATIN) {
return false;
}
}
return true;
}
/**
* Determines whether the given name can appear on the right side of
* the dot operator. Many properties (like reserved words) cannot.
*/
static boolean isValidPropertyName(String name) {
return TokenStream.isJSIdentifier(name) &&
!TokenStream.isKeyword(name) &&
// no Unicode escaped characters - some
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> browsers are less tolerant
// of Unicode characters that might be valid according to the
// language spec.
// Note that by this point, unicode escapes have been converted
// to UTF-16 characters, so we're only searching for character
// values, not escapes.
isLatin(name);
}
private static class VarCollector implements Visitor {
final Map<String, Node> vars = Maps.newLinkedHashMap();
public void visit(Node n) {
if (n.getType() == Token.NAME) {
Node parent = n.getParent();
if (parent != null && parent.getType() == Token.VAR) {
String name = n.getString();
if (!vars.containsKey(name)) {
vars.put(name, n);
}
}
}
}
}
/**
* Retrieves vars declared in the current node tree, excluding descent scopes.
*/
public static Collection<Node> getVarsDeclaredInBranch(Node root) {
VarCollector collector = new VarCollector();
visitPreOrder(
root,
collector,
Predicates.<Node>not(new MatchNodeType(Token.FUNCTION)));
return collector.vars.values();
}
/**
* @return {@code true} if the node an assignment to a prototype property of
* some constructor.
*/
static boolean isPrototypePropertyDeclaration(Node n) {
if (!isExprAssign(n)) {
return false;
}
return isPrototypeProperty(n.getFirstChild().getFirstChild());
}
static boolean isPrototypeProperty(Node n) {
String lhsString = n.getQualifiedName();
if (lhsString == null) {
return false;
}
int prototypeIdx = lhsString.indexOf(".prototype.");
return prototypeIdx != -1;
}
/**
* @return The class name part of a qualified prototype name.
*/
static Node getPrototypeClassName(Node qName) {
Node cur = qName;
while (isGetProp(cur)) {
if (cur.getLastChild().getString().equals("prototype")) {
return cur.getFirstChild();
} else {
cur = cur.getFirstChild();
}
}
return null;
}
/**
* @return The string property name part of a qualified prototype name.
*/
static String getPrototypePropertyName(Node
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> qName) {
String qNameStr = qName.getQualifiedName();
int prototypeIdx = qNameStr.lastIndexOf(".prototype.");
int memberIndex = prototypeIdx + ".prototype".length() + 1;
return qNameStr.substring(memberIndex);
}
/**
* Create a node for an empty result expression:
* "void 0"
*/
static Node newUndefinedNode(Node srcReferenceNode) {
// TODO(johnlenz): Why this instead of the more common "undefined"?
Node node = new Node(Token.VOID, Node.newNumber(0));
if (srcReferenceNode != null) {
node.copyInformationFromForTree(srcReferenceNode);
}
return node;
}
/**
* Create a VAR node containing the given name and initial value expression.
*/
static Node newVarNode(String name, Node value) {
Node nodeName = Node.newString(Token.NAME, name);
if (value != null) {
Preconditions.checkState(value.getNext() == null);
nodeName.addChildToBack(value);
nodeName.copyInformationFrom(value);
}
Node var = new Node(Token.VAR, nodeName)
.copyInformationFrom(nodeName);
return var;
}
/**
* A predicate for matching name nodes with the specified node.
*/
private static class MatchNameNode implements Predicate<Node>{
final String name;
MatchNameNode(String name){
this.name = name;
}
public boolean apply(Node n) {
return n.getType() == Token.NAME
&& n.getString().equals(name);
}
}
/**
* A predicate for matching nodes with the specified type.
*/
static class MatchNodeType implements Predicate<Node>{
final int type;
MatchNodeType(int type){
this.type = type;
}
public boolean apply(Node n) {
return n.getType() == type;
}
}
/**
* Whether a Node type is within the node tree.
*/
static boolean isNodeTypeReferenced(Node node, int type) {
return isNodeTypeReferenced(node, type, Predicates.<Node>alwaysTrue());
}
/**
* Whether a Node type is within the node tree.
*/
static boolean isNodeTypeReferenced(
Node node, int
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> node);
}
/**
* A pre-order traversal, calling Vistor.visit for each child matching
* the predicate.
*/
static void visitPreOrder(Node node,
Visitor vistor,
Predicate<Node> traverseChildrenPred) {
vistor.visit(node);
if (traverseChildrenPred.apply(node)) {
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
visitPreOrder(c, vistor, traverseChildrenPred);
}
}
}
/**
* A post-order traversal, calling Vistor.visit for each child matching
* the predicate.
*/
static void visitPostOrder(Node node,
Visitor vistor,
Predicate<Node> traverseChildrenPred) {
if (traverseChildrenPred.apply(node)) {
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
visitPostOrder(c, vistor, traverseChildrenPred);
}
}
vistor.visit(node);
}
/**
* @return Whether a TRY node has a finally block.
*/
static boolean hasFinally(Node n) {
Preconditions.checkArgument(n.getType() == Token.TRY);
return n.getChildCount() == 3;
}
/**
* @return The BLOCK node containing the CATCH node (if any)
* of a TRY.
*/
static Node getCatchBlock(Node n) {
Preconditions.checkArgument(n.getType() == Token.TRY);
return n.getFirstChild().getNext();
}
/**
* @return Whether BLOCK (from a TRY node) contains a CATCH.
* @see NodeUtil#getCatchBlock
*/
static boolean hasCatchHandler(Node n) {
Preconditions.checkArgument(n.getType() == Token.BLOCK);
return n.hasChildren() && n.getFirstChild().getType() == Token.CATCH;
}
/**
* @param fnNode The function.
* @return The Node containing the Function parameters.
*/
static Node getFnParameters(Node fnNode) {
// Function NODE: [ FUNCTION -> NAME, LP -> ARG1, ARG2, ... ]
Preconditions.checkArgument(fnNode.getType() == Token.FUNCTION);
return fnNode.getFirstChild().getNext
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>, null);
}
void add(String str) {
cc.add(str);
}
private void addIdentifier(String identifier) {
cc.addIdentifier(identifierEscape(identifier));
}
void add(Node n) {
add(n, Context.OTHER);
}
void add(Node n, Context context) {
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(childCount == 2);
int p = NodeUtil.precedence(type);
addLeftExpr(first, p, context);
cc.addOp(opstr, true);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
// Handle associativity.
// e.g. if the parse tree is a * (b * c),
// we can simply generate a * b * c.
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
// Assignments are the only right-associative binary operators
addExpr(last, p, rhsContext);
} else {
addExpr(last, p + 1, rhsContext);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
Preconditions.checkState(first.getNext().getType() == Token.BLOCK &&
first.getNext().getChildCount() <= 1);
Preconditions.checkState(childCount >= 2 && childCount <= 3);
add("try");
add(first, Context.PRESERVE_BLOCK);
// second child contains the catch block, or nothing if there
// isn't a catch block
Node catchblock = first.getNext().getFirstChild();
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> if (catchblock != null) {
add(catchblock);
}
if (childCount == 3) {
add("finally");
add(last, Context.PRESERVE_BLOCK);
}
break;
}
case Token.CATCH:
Preconditions.checkState(childCount == 3);
if (first.getNext().getType() != Token.EMPTY) {
throw new Error("Catch conditions not suppored because I think" +
" that it may be a netscape only feature.");
}
add("catch(");
add(first);
add(")");
add(last, Context.PRESERVE_BLOCK);
break;
case Token.THROW:
Preconditions.checkState(childCount == 1);
add("throw");
add(first);
// Must have a ';' after a throw statement, otherwise safari can't
// parse this.
cc.endStatement(true);
break;
case Token.RETURN:
add("return");
if (childCount == 1) {
add(first);
} else {
Preconditions.checkState(childCount == 0);
}
cc.endStatement();
break;
case Token.VAR:
if (first != null) {
add("var ");
addList(first, false, getContextForNoInOperator(context));
}
break;
case Token.LABEL_NAME:
Preconditions.checkState(!n.getString().isEmpty());
addIdentifier(n.getString());
break;
case Token.NAME:
if (first == null || first.getType() == Token.EMPTY) {
addIdentifier(n.getString());
} else {
Preconditions.checkState(childCount == 1);
addIdentifier(n.getString());
cc.addOp("=", true);
if (first.getType() == Token.COMMA) {
addExpr(first, NodeUtil.precedence(Token.ASSIGN));
} else {
// Add expression, consider nearby code at lowest level of
// precedence.
addExpr(first, 0, getContextForNoInOperator(context));
}
}
break;
case Token.ARRAYLIT:
add("[");
addList(first, (int[]) n.getProp(Node.SKIP_INDEXES_PROP));
add("]");
break;
case Token
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>.LP:
add("(");
addList(first);
add(")");
break;
case Token.COMMA:
addList(first, false, context);
break;
case Token.NUMBER:
Preconditions.checkState(childCount == 0);
cc.addNumber(n.getDouble());
break;
case Token.TYPEOF:
case Token.VOID:
case Token.NOT:
case Token.BITNOT:
case Token.POS:
case Token.NEG: {
// All of these unary operators are right-associative
Preconditions.checkState(childCount == 1);
cc.addOp(NodeUtil.opToStrNoFail(type), false);
addExpr(first, NodeUtil.precedence(type));
break;
}
case Token.HOOK: {
Preconditions.checkState(childCount == 3);
int p = NodeUtil.precedence(type);
addLeftExpr(first, p + 1, context);
cc.addOp("?", true);
addExpr(first.getNext(), p);
cc.addOp(":", true);
addExpr(last, p);
break;
}
case Token.REGEXP:
if (first.getType() != Token.STRING ||
last.getType() != Token.STRING) {
throw new Error("Expected children to be strings");
}
String regexp = regexpEscape(first.getString(), outputCharsetEncoder);
// I only use one .add because whitespace matters
if (childCount == 2) {
add(regexp + last.getString());
} else {
Preconditions.checkState(childCount == 1);
add(regexp);
}
break;
case Token.GET_REF:
add(first);
break;
case Token.REF_SPECIAL:
Preconditions.checkState(childCount == 1);
add(first);
add(".");
add((String) n.getProp(Node.NAME_PROP));
break;
case Token.FUNCTION:
if (n.getClass() != Node.class) {
throw new Error("Unexpected Node subclass.");
}
Preconditions.checkState(childCount == 3);
boolean funcNeedsParens = (context == Context.START_OF_EXPR);
if (funcNeedsParens) {
add
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>("(");
}
add("function");
add(first);
add(first.getNext());
add(last, Context.PRESERVE_BLOCK);
cc.endFunction(context == Context.STATEMENT);
if (funcNeedsParens) {
add(")");
}
break;
case Token.SCRIPT:
case Token.BLOCK: {
if (n.getClass() != Node.class) {
throw new Error("Unexpected Node subclass.");
}
boolean stripBlock = n.isSyntheticBlock() ||
((context != Context.PRESERVE_BLOCK) && (n.getChildCount() < 2));
if (!stripBlock) {
cc.beginBlock();
}
for (Node c = first; c != null; c = c.getNext()) {
add(c, Context.STATEMENT);
// VAR doesn't include ';' since it gets used in expressions
if (c.getType() == Token.VAR) {
cc.endStatement();
}
if (c.getType() == Token.FUNCTION) {
cc.maybeLineBreak();
}
// Prefer to break lines in between top-level statements
// because top level statements are more homogeneous.
if (type == Token.SCRIPT) {
cc.notePreferredLineBreak();
}
}
if (!stripBlock) {
cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT));
}
break;
}
case Token.FOR:
if (childCount == 4) {
add("for(");
if (first.getType() == Token.VAR) {
add(first, Context.IN_FOR_INIT_CLAUSE);
} else {
addExpr(first, 0, Context.IN_FOR_INIT_CLAUSE);
}
add(";");
add(first.getNext());
add(";");
add(first.getNext().getNext());
add(")");
addNonEmptyExpression(
last, getContextForNonEmptyExpression(context), false);
} else {
Preconditions.checkState(childCount == 3);
add("for(");
add(first);
add("in");
add(first.getNext());
add(")");
addNonEmptyExpression(
last, getContextForNonEmptyExpression(context), false);
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> }
break;
case Token.DO:
Preconditions.checkState(childCount == 2);
add("do");
addNonEmptyExpression(first, Context.OTHER, false);
add("while(");
add(last);
add(")");
cc.endStatement();
break;
case Token.WHILE:
Preconditions.checkState(childCount == 2);
add("while(");
add(first);
add(")");
addNonEmptyExpression(
last, getContextForNonEmptyExpression(context), false);
break;
case Token.EMPTY:
Preconditions.checkState(childCount == 0);
break;
case Token.GETPROP: {
Preconditions.checkState(childCount == 2);
Preconditions.checkState(last.getType() == Token.STRING);
boolean needsParens = (first.getType() == Token.NUMBER);
if (needsParens) {
add("(");
}
addLeftExpr(first, NodeUtil.precedence(type), context);
if (needsParens) {
add(")");
}
add(".");
addIdentifier(last.getString());
break;
}
case Token.GETELEM:
Preconditions.checkState(childCount == 2);
addLeftExpr(first, NodeUtil.precedence(type), context);
add("[");
add(first.getNext());
add("]");
break;
case Token.WITH:
Preconditions.checkState(childCount == 2);
add("with(");
add(first);
add(")");
addNonEmptyExpression(
last, getContextForNonEmptyExpression(context), false);
break;
case Token.INC:
case Token.DEC: {
Preconditions.checkState(childCount == 1);
String o = type == Token.INC ? "++" : "--";
int postProp = n.getIntProp(Node.INCRDECR_PROP, 0);
// A non-zero post-prop value indicates a post inc/dec, default of zero
// is a pre-inc/dec.
if (postProp != 0) {
addLeftExpr(first, NodeUtil.precedence(type), context);
cc.addOp(o, false);
} else {
cc.addOp(o
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>, false);
add(first);
}
break;
}
case Token.CALL:
// If the left hand side of the call is a direct reference to eval,
// then it must have a DIRECT_EVAL annotation. If it does not, then
// that means it was originally an indirect call to eval, and that
// indirectness must be preserved.
if (first.getType() == Token.NAME &&
"eval".equals(first.getString()) &&
!first.getBooleanProp(Node.DIRECT_EVAL)) {
add("(0,eval)");
} else {
addLeftExpr(first, NodeUtil.precedence(type), context);
}
add("(");
addList(first.getNext());
add(")");
break;
case Token.IF:
boolean hasElse = childCount == 3;
boolean ambiguousElseClause =
context == Context.BEFORE_DANGLING_ELSE && !hasElse;
if (ambiguousElseClause) {
cc.beginBlock();
}
add("if(");
add(first);
add(")");
if (hasElse) {
addNonEmptyExpression(
first.getNext(), Context.BEFORE_DANGLING_ELSE, false);
add("else");
addNonEmptyExpression(
last, getContextForNonEmptyExpression(context), false);
} else {
addNonEmptyExpression(first.getNext(), Context.OTHER, false);
Preconditions.checkState(childCount == 2);
}
if (ambiguousElseClause) {
cc.endBlock();
}
break;
case Token.NULL:
case Token.THIS:
case Token.FALSE:
case Token.TRUE:
Preconditions.checkState(childCount == 0);
add(Node.tokenToName(type));
break;
case Token.CONTINUE:
Preconditions.checkState(childCount <= 1);
add("continue");
if (childCount == 1) {
if (first.getType() != Token.LABEL_NAME) {
throw new Error("Unexpected token type. Should be LABEL_NAME.");
}
add(" ");
add(first);
}
cc.endStatement();
break;
case Token.DEBUGGER:
Preconditions.checkState(childCount == 0);
add("debugger");
cc
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>.endStatement();
break;
case Token.BREAK:
Preconditions.checkState(childCount <= 1);
add("break");
if (childCount == 1) {
if (first.getType() != Token.LABEL_NAME) {
throw new Error("Unexpected token type. Should be LABEL_NAME.");
}
add(" ");
add(first);
}
cc.endStatement();
break;
case Token.EXPR_VOID:
case Token.EXPR_RESULT:
if (type == Token.EXPR_VOID) {
throw new Error("Unexpected EXPR_VOID. Should be EXPR_RESULT.");
}
Preconditions.checkState(childCount == 1);
add(first, Context.START_OF_EXPR);
cc.endStatement();
break;
case Token.NEW:
add("new ");
int precedence = NodeUtil.precedence(type);
// If the first child contains a CALL, then claim higher precedence
// to force parens. Otherwise, when parsed, NEW will bind to the
// first viable parens
if (NodeUtil.containsCall(first)) {
precedence = NodeUtil.precedence(first.getType()) + 1;
}
addExpr(first, precedence);
// '()' is optional when no arguments are present
Node next = first.getNext();
if (next != null) {
add("(");
addList(next);
add(")");
}
break;
case Token.STRING:
Preconditions.checkState(childCount == 0);
add(jsString(n.getString(), outputCharsetEncoder));
break;
case Token.DELPROP:
Preconditions.checkState(childCount == 1);
add("delete ");
add(first);
break;
case Token.OBJECTLIT: {
Preconditions.checkState(childCount % 2 == 0);
boolean needsParens = (context == Context.START_OF_EXPR);
if (needsParens) {
add("(");
}
add("{");
for (Node c = first; c != null; c = c.getNext().getNext()) {
if (c != first) {
cc.listSeparator();
}
// Object literal property names don't have to be quoted if they are
// not JavaScript keywords
if (
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>c.getType() == Token.STRING &&
!TokenStream.isKeyword(c.getString()) &&
TokenStream.isJSIdentifier(c.getString()) &&
// do not encode literally any non-literal characters that were
// unicode escaped.
NodeUtil.isLatin(c.getString())) {
add(c.getString());
} else {
addExpr(c, 1);
}
add(":");
addExpr(c.getNext(), 1);
}
add("}");
if (needsParens) {
add(")");
}
break;
}
case Token.SWITCH:
add("switch(");
add(first);
add(")");
cc.beginBlock();
addAllSiblings(first.getNext());
cc.endBlock(context == Context.STATEMENT);
break;
case Token.CASE:
Preconditions.checkState(childCount == 2);
add("case ");
add(first);
addCaseBody(last);
break;
case Token.DEFAULT:
Preconditions.checkState(childCount == 1);
add("default");
addCaseBody(first);
break;
case Token.LABEL:
Preconditions.checkState(childCount == 2);
if (first.getType() != Token.LABEL_NAME) {
throw new Error("Unexpected token type. Should be LABEL_NAME.");
}
add(first);
add(":");
addNonEmptyExpression(
last, getContextForNonEmptyExpression(context), true);
break;
// This node is auto generated in anonymous functions and should just get
// ignored for our purposes.
case Token.SETNAME:
break;
default:
throw new Error("Unknown type " + type + "\n" + n.toStringTree());
}
cc.endSourceMapping(n);
}
/**
* Adds a block or expression, substituting a VOID with an empty statement.
* This is used for "for (...);" and "if (...);" type statements.
*
* @param n The node to print.
* @param context The context to determine how the node should be printed.
*/
private void addNonEmptyExpression(
Node n, Context context, boolean allowNonBlockChild) {
Node nodeToProcess = n;
if (!allowNonBlockChild && n.getType
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>() != Token.BLOCK) {
throw new Error("Missing BLOCK child.");
}
// Strip unneeded blocks, that is blocks with <2 children unless
// the CodePrinter specifically wants to keep them.
if (n.getType() == Token.BLOCK ) {
int count = getNonEmptyChildCount(n);
if (count == 0) {
if (cc.shouldPreserveExtraBlocks()) {
cc.beginBlock();
cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT));
} else {
cc.endStatement(true);
}
return;
}
if (count == 1) {
// Hack around a couple of browser bugs:
// Safari needs a block around function declarations.
// IE6/7 needs a block around DOs.
Node firstAndOnlyChild = getFirstNonEmptyChild(n);
boolean alwaysWrapInBlock = cc.shouldPreserveExtraBlocks();
if (alwaysWrapInBlock ||
firstAndOnlyChild.getType() == Token.FUNCTION ||
firstAndOnlyChild.getType() == Token.DO) {
cc.beginBlock();
add(firstAndOnlyChild, Context.STATEMENT);
cc.maybeLineBreak();
cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT));
return;
} else {
// Continue with the only child.
nodeToProcess = firstAndOnlyChild;
}
}
}
if (nodeToProcess.getType() == Token.EMPTY) {
cc.endStatement(true);
} else {
add(nodeToProcess, context);
// VAR doesn't include ';' since it gets used in expressions - so any
// VAR in a statement context needs a call to endStatement() here.
if (nodeToProcess.getType() == Token.VAR) {
cc.endStatement();
}
}
}
/**
* Adds a node at the left-hand side of an expression. Unlike
* {@link #addExpr(Node,int)}, this preserves information about the context.
*
* The left side of an expression is special because in the JavaScript
* grammar, certain tokens may be parsed differently when they are at
* the beginning of a statement. For example, "{}" is parsed as a block,
* but
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> "{'x': 'y'}" is parsed as an object literal.
*/
void addLeftExpr(Node n, int minPrecedence, Context context) {
addExpr(n, minPrecedence, context);
}
void addExpr(Node n, int minPrecedence) {
addExpr(n, minPrecedence, Context.OTHER);
}
private void addExpr(Node n, int minPrecedence, Context context) {
if ((NodeUtil.precedence(n.getType()) < minPrecedence) ||
((context == Context.IN_FOR_INIT_CLAUSE) &&
(n.getType() == Token.IN))){
add("(");
add(n, clearContextForNoInOperator(context));
add(")");
} else {
add(n, context);
}
}
void addList(Node firstInList) {
addList(firstInList, true, Context.OTHER);
}
void addList(Node firstInList, boolean isArrayOrFunctionArgument) {
addList(firstInList, isArrayOrFunctionArgument, Context.OTHER);
}
void addList(Node firstInList, boolean isArrayOrFunctionArgument,
Context lhsContext) {
for (Node n = firstInList; n != null; n = n.getNext()) {
boolean isFirst = n == firstInList;
if (isFirst) {
addLeftExpr(n, isArrayOrFunctionArgument ? 1 : 0, lhsContext);
} else {
cc.listSeparator();
addExpr(n, isArrayOrFunctionArgument ? 1 : 0);
}
}
}
/**
* This function adds a comma-separated list as is specified by an ARRAYLIT
* node with the associated skipIndexes array. This is a space optimization
* since we avoid creating a whole Node object for each empty array literal
* slot.
* @param firstInList The first in the node list (chained through the next
* property).
* @param skipIndexes If not null, then the array of skipped entries in the
* array.
*/
void addList(Node firstInList, int[] skipIndexes) {
int nextSlot = 0;
int nextSkipSlot = 0;
for (Node n = firstInList; n != null; n
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> than using the CharsetEncoder.
if (c > 0x1f && c <= 0x7f) {
sb.append(c);
} else {
// Other characters can be misinterpreted by some js parsers,
// or perhaps mangled by proxies along the way,
// so we play it safe and unicode escape them.
appendHexJavaScriptRepresentation(sb, c);
}
}
}
}
sb.append(quote);
return sb.toString();
}
static String identifierEscape(String s) {
// First check if escaping is needed at all -- in most cases it isn't.
if (NodeUtil.isLatin(s)) {
return s;
}
// Now going through the string to escape non-latin characters if needed.
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
// Identifiers should always go to Latin1/ ASCII characters because
// different browser's rules for valid identifier characters are
// crazy.
if (c > 0x1F && c < 0x7F) {
sb.append(c);
} else {
appendHexJavaScriptRepresentation(sb, c);
}
}
return sb.toString();
}
/** Gets the number of children of this node that are non empty. */
private static int getNonEmptyChildCount(Node n) {
int i = 0;
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (c.getType() != Token.EMPTY) {
i++;
}
}
return i;
}
/** Gets the first non-empty child of the given node. */
private static Node getFirstNonEmptyChild(Node n) {
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (c.getType() != Token.EMPTY) {
return c;
}
}
return null;
}
// Information on the current context. Used for disambiguating special cases.
// For example, a "{" could indicate the start of an object literal or a
// block, depending on the current context.
enum Context {
STATEMENT,
BE
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> Token.SCRIPT) {
requiresLineNumbers = true;
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.SCRIPT) {
requiresLineNumbers = false;
} else if (requiresLineNumbers) {
if (n.getLineno() == -1) {
// The tree version of the node is really the best diagnostic
// info we have to offer here.
compiler.report(
t.makeError(n, MISSING_LINE_INFO,
n.toStringTree()));
}
}
}
}
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>.getType() == Token.GETPROP) {
methodName = callName.getLastChild().getString();
} else if (callName.getType() == Token.NAME) {
String name = callName.getString();
int dollarIndex = name.lastIndexOf('$');
if (dollarIndex != -1) {
methodName = name.substring(dollarIndex + 1);
}
}
if (methodName != null) {
if (methodName.equals("inherits")) {
return SubclassType.INHERITS;
} else if (methodName.equals("mixin")) {
return SubclassType.MIXIN;
}
}
return null;
}
@Override
public boolean isSuperClassReference(String propertyName) {
return "superClass_".equals(propertyName);
}
/**
* Given a qualified name node, strip "prototype" off the end.
*
* Examples of this transformation:
* a.b.c => a.b.c
* a.b.c.prototype => a.b.c
*/
private Node stripPrototype(Node qualifiedName) {
if (qualifiedName.getType() == Token.GETPROP &&
qualifiedName.getLastChild().getString().equals("prototype")) {
return qualifiedName.getFirstChild();
}
return qualifiedName;
}
/**
* Exctracts X from goog.provide('X'), if the applied Node is goog.
*
* @return The extracted class name, or null.
*/
@Override
public String extractClassNameIfProvide(Node node, Node parent){
return extractClassNameIfGoog(node, parent, "goog.provide");
}
/**
* Exctracts X from goog.require('X'), if the applied Node is goog.
*
* @return The extracted class name, or null.
*/
@Override
public String extractClassNameIfRequire(Node node, Node parent){
return extractClassNameIfGoog(node, parent, "goog.require");
}
private static String extractClassNameIfGoog(Node node, Node parent,
String functionName){
String className = null;
if (NodeUtil.isExprCall(parent)) {
Node callee = node.getFirstChild();
if (callee != null && callee.getType() == Token.GETPROP) {
String qualifiedName = callee
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>TreeTypeAwareEqualsImpl(n2);
if (res != null) {
return res;
}
}
return res;
}
public static String tokenToName(int token) {
switch (token) {
case Token.ERROR: return "error";
case Token.EOF: return "eof";
case Token.EOL: return "eol";
case Token.ENTERWITH: return "enterwith";
case Token.LEAVEWITH: return "leavewith";
case Token.RETURN: return "return";
case Token.GOTO: return "goto";
case Token.IFEQ: return "ifeq";
case Token.IFNE: return "ifne";
case Token.SETNAME: return "setname";
case Token.BITOR: return "bitor";
case Token.BITXOR: return "bitxor";
case Token.BITAND: return "bitand";
case Token.EQ: return "eq";
case Token.NE: return "ne";
case Token.LT: return "lt";
case Token.LE: return "le";
case Token.GT: return "gt";
case Token.GE: return "ge";
case Token.LSH: return "lsh";
case Token.RSH: return "rsh";
case Token.URSH: return "ursh";
case Token.ADD: return "add";
case Token.SUB: return "sub";
case Token.MUL: return "mul";
case Token.DIV: return "div";
case Token.MOD: return "mod";
case Token.BITNOT: return "bitnot";
case Token.NEG: return "neg";
case Token.NEW: return "new";
case Token.DELPROP: return "delprop";
case Token.TYPEOF: return "typeof";
case Token.GETPROP: return "getprop";
case Token.SETPROP: return "setprop";
case Token.GETELEM: return "getelem";
case Token.SETELEM: return "setelem";
case Token.CALL: return "call";
case Token.NAME: return "name";
case Token.NUMBER
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>";
case Token.COLON: return "colon";
case Token.OR: return "or";
case Token.AND: return "and";
case Token.INC: return "inc";
case Token.DEC: return "dec";
case Token.DOT: return "dot";
case Token.FUNCTION: return "function";
case Token.EXPORT: return "export";
case Token.IMPORT: return "import";
case Token.IF: return "if";
case Token.ELSE: return "else";
case Token.SWITCH: return "switch";
case Token.CASE: return "case";
case Token.DEFAULT: return "default";
case Token.WHILE: return "while";
case Token.DO: return "do";
case Token.FOR: return "for";
case Token.BREAK: return "break";
case Token.CONTINUE: return "continue";
case Token.VAR: return "var";
case Token.WITH: return "with";
case Token.CATCH: return "catch";
case Token.FINALLY: return "finally";
case Token.RESERVED: return "reserved";
case Token.NOT: return "not";
case Token.VOID: return "void";
case Token.BLOCK: return "block";
case Token.ARRAYLIT: return "arraylit";
case Token.OBJECTLIT: return "objectlit";
case Token.LABEL: return "label";
case Token.TARGET: return "target";
case Token.LOOP: return "loop";
case Token.EXPR_VOID: return "expr_void";
case Token.EXPR_RESULT: return "expr_result";
case Token.JSR: return "jsr";
case Token.SCRIPT: return "script";
case Token.EMPTY: return "empty";
case Token.GET_REF: return "get_ref";
case Token.REF_SPECIAL: return "ref_special";
}
return "<unknown="+token+">";
}
/** Returns true if this node is equivalent semantically to another */
public boolean isEquivalentTo(Node node) {
if (type == Token.ARRAYLIT) {
try {
int
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ENTERWITH:
case Token.LEAVEWITH:
case Token.RETURN:
case Token.GOTO:
case Token.IFEQ:
case Token.IFNE:
case Token.NEW:
case Token.DELPROP:
case Token.SETNAME:
case Token.SETPROP:
case Token.SETELEM:
case Token.CALL:
case Token.THROW:
case Token.RETHROW:
case Token.SETVAR:
case Token.CATCH_SCOPE:
case Token.RETURN_RESULT:
case Token.SET_REF:
case Token.DEL_REF:
case Token.REF_CALL:
case Token.TRY:
case Token.SEMI:
case Token.INC:
case Token.DEC:
case Token.EXPORT:
case Token.IMPORT:
case Token.IF:
case Token.ELSE:
case Token.SWITCH:
case Token.WHILE:
case Token.DO:
case Token.FOR:
case Token.BREAK:
case Token.CONTINUE:
case Token.VAR:
case Token.CONST:
case Token.WITH:
case Token.CATCH:
case Token.FINALLY:
case Token.BLOCK:
case Token.LABEL:
case Token.TARGET:
case Token.LOOP:
case Token.JSR:
case Token.SETPROP_OP:
case Token.SETELEM_OP:
case Token.LOCAL_BLOCK:
case Token.SET_REF_OP:
return true;
default:
return false;
}
}
/**
* This function takes a set of GETPROP nodes and produces a string that is
* each property separated by dots. If the node ultimately under the left
* sub-tree is not a simple name, this is not a valid qualified name.
*
* @return a null if this is not a qualified name, or a dot-separated string
* of the name and properties.
*/
public String getQualifiedName() {
if (type == Token.NAME) {
return getString();
} else if (type == Token.GETPROP) {
String left = getFirstChild().getQualifiedName();
if (left == null) {
return
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS> null;
}
return left + "." + getLastChild().getString();
} else if (type == Token.THIS) {
return "this";
} else {
return null;
}
}
/**
* Returns whether a node corresponds to a simple or a qualified name, such as
* <code>x</code> or <code>a.b.c</code> or <code>this.a</code>.
*/
public boolean isQualifiedName() {
switch (getType()) {
case Token.NAME:
case Token.THIS:
return true;
case Token.GETPROP:
return getFirstChild().isQualifiedName();
default:
return false;
}
}
/**
* Returns whether a node corresponds to a simple or a qualified name without
* a "this" reference, such as <code>a.b.c</code>, but not <code>this.a</code>
* .
*/
public boolean isUnscopedQualifiedName() {
switch (getType()) {
case Token.NAME:
return true;
case Token.GETPROP:
return getFirstChild().isUnscopedQualifiedName();
default:
return false;
}
}
// ==========================================================================
// Mutators
/**
* Removes this node from its parent. Equivalent to:
* node.getParent().removeChild();
*/
public Node detachFromParent() {
Preconditions.checkState(parent != null);
parent.removeChild(this);
return this;
}
/**
* Removes the first child of Node. Equivalent to:
* node.removeChild(node.getFirstChild());
*
* @return The removed Node.
*/
public Node removeFirstChild() {
Node child = first;
if (child != null) {
removeChild(child);
}
return child;
}
/**
* @return A Node that is the head of the list of children.
*/
public Node removeChildren() {
Node children = first;
for (Node child = first; child != null; child = child.getNext()) {
child.parent = null;
}
first = null;
last = null;
return children;
}
/**
* Removes all children from this node and isolates the children from each
* other.
*/
public void detachChildren() {
for (
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>")
public Set<String> getDirectives() {
return (Set<String>) getProp(DIRECTIVES);
}
/**
* Sets the warning suppressions on this node.
*/
public void setSuppressions(Set<String> val) {
Preconditions.checkState(type == Token.FUNCTION || type == Token.SCRIPT);
putProp(SUPPRESSIONS, val);
}
/**
* Returns the set of supressions for this node.
*/
@SuppressWarnings("unchecked")
public Set<String> getSuppressions() {
return (Set<String>) getProp(SUPPRESSIONS);
}
/**
* Sets whether this is a synthetic block that should not be considered
* a real source block.
*/
public void setWasEmptyNode(boolean val) {
putBooleanProp(EMPTY_BLOCK, val);
}
/**
* Returns whether this is a synthetic block that should not be considered
* a real source block.
*/
public boolean wasEmptyNode() {
return getBooleanProp(EMPTY_BLOCK);
}
/**
* Marks this function or constructor call node as having no side effects.
* This property is only meaningful for {@link Token#CALL} and
* {@link Token#NEW} nodes.
*/
public void setIsNoSideEffectsCall() {
Preconditions.checkArgument(
getType() == Token.CALL || getType() == Token.NEW,
"setIsNoSideEffectsCall only supports CALL and NEW nodes, got " +
Token.name(getType()));
putBooleanProp(NO_SIDE_EFFECTS_CALL, true);
}
/**
* Returns true if this node is a function or constructor call that
* has no side effects.
*/
public boolean isNoSideEffectsCall() {
return getBooleanProp(NO_SIDE_EFFECTS_CALL);
}
/**
* This should only be called for STRING nodes created in object lits.
*/
public boolean isQuotedString() {
return false;
}
/**
* This should only be called for STRING nodes created in object lits.
*/
public void setQuotedString() {
Kit.codeBug();
}
static class NodeMismatch {
final Node nodeA;
final Node nodeB;
NodeMismatch(Node nodeA, Node nodeB) {
this.nodeA = node
Closure, 100
<FILEB>
<CHANGES>
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN)) {
return false;
}
<CHANGEE>
<CHANGES>
return parent!= null && NodeUtil.isGet(parent);
<CHANGEE>
<FILEE>
<FILEB>
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @this
// annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.hasThisType())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {};
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
<CHANGES>
return false;
<CHANGEE>
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
*<SCANS>ObjectType type : implementedInterfaces) {
registry.registerTypeImplementingInterface(this, type);
}
this.implementedInterfaces = ImmutableList.copyOf(implementedInterfaces);
}
@Override
public boolean hasProperty(String name) {
return super.hasProperty(name) || "prototype".equals(name);
}
@Override
public boolean hasOwnProperty(String name) {
return super.hasOwnProperty(name) || "prototype".equals(name);
}
@Override
public JSType getPropertyType(String name) {
if ("prototype".equals(name)) {
return getPrototype();
} else {
if (!hasOwnProperty(name)) {
if ("call".equals(name)) {
// Define the "call" function lazily.
Node params = getParametersNode();
if (params == null) {
// If there's no params array, don't do any type-checking
// in this CALL function.
defineDeclaredProperty(name,
new FunctionBuilder(registry)
.withReturnType(getReturnType())
.build(),
false);
} else {
params = params.cloneTree();
Node thisTypeNode = Node.newString(Token.NAME, "thisType");
thisTypeNode.setJSType(
registry.createOptionalNullableType(getTypeOfThis()));
params.addChildToFront(thisTypeNode);
thisTypeNode.setOptionalArg(true);
defineDeclaredProperty(name,
new FunctionBuilder(registry)
.withParamsNode(params)
.withReturnType(getReturnType())
.build(),
false);
}
} else if ("apply".equals(name)) {
// Define the "apply" function lazily.
FunctionParamBuilder builder = new FunctionParamBuilder(registry);
// Ecma-262 says that apply's second argument must be an Array
// or an arguments object. We don't model the arguments object,
// so let's just be forgiving for now.
// TODO(nicksantos): Model the Arguments object.
builder.addOptionalParams(
registry.createNullableType(getTypeOfThis()),
registry.createNullableType(
registry.getNativeType(JSTypeNative.OBJECT_TYPE)));
defineDeclaredProperty(name,
new FunctionBuilder(registry)
.withParams(builder)
.withReturnType(getReturnType